Java Tutorial
🔍

Java protected Modifier

Java protected Modifier

protected sits exactly between default and public in Java's visibility spectrum. A protected member is accessible to all classes in the same package — just like default — plus one extra group: subclasses in any package, regardless of where they are.

This dual nature is what makes protected useful and also what makes it subtle. Same-package access is easy to understand. The cross-package subclass access has a specific rule: a subclass in a different package can access a protected member only through its own inheritance — not through an arbitrary reference to the parent type. That distinction matters and appears in interviews regularly.

The protected Access Rule — Exactly What It Permits

Who can access a protected member?

  SAME PACKAGE:
    Any class in the same package — identical to default access.
    No inheritance required.

  DIFFERENT PACKAGE — SUBCLASS ONLY:
    A subclass CAN access protected members through:
      — super.method()           ← call parent's protected method
      — super.field              ← read parent's protected field
      — this.field               ← own inherited protected field
      — new ChildInstance.field  ← through a subclass-typed reference

    A subclass CANNOT access protected members through:
      — new ParentInstance.field ← arbitrary parent reference from diff package
      — new SiblingChild.field   ← sibling subclass reference from diff package

  EVERYWHERE ELSE: not accessible (no package, no inheritance)

Quick memory rule:
  private      → this class only
  default      → this package
  protected    → this package + subclasses anywhere
  public       → everywhere

1 — protected Fields and Methods: Inheritance Hook

protected fields and methods are the tools for building extensible class hierarchies. The parent class provides a stable public API for external callers, and protected members that subclasses can use and override.

// File: com/devstackflow/base/Shape.java package com.devstackflow.base; public abstract class Shape { // protected — subclasses in any package can read and write protected String color; protected boolean filled; // protected constructor — subclasses call super(...) protected Shape(String color, boolean filled) { this.color = color; this.filled = filled; } // protected method — subclasses can override this hook protected String getShapeInfo() { return color + (filled ? " filled" : " unfilled"); } // abstract — subclasses must provide implementation public abstract double area(); public abstract double perimeter(); // public final — concrete template method using protected hook public final void printDetails() { System.out.printf("Shape : %s%n", getClass().getSimpleName()); System.out.printf("Info : %s%n", getShapeInfo()); System.out.printf("Area : %.2f sq units%n", area()); System.out.printf("Perim : %.2f units%n%n", perimeter()); } }
// File: com/devstackflow/shapes/Circle.java package com.devstackflow.shapes; // DIFFERENT package from Shape import com.devstackflow.base.Shape; public class Circle extends Shape { private double radius; public Circle(double radius, String color, boolean filled) { super(color, filled); // protected constructor — accessible via super() this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } @Override public double perimeter() { return 2 * Math.PI * radius; } // Overrides protected method — adds circle-specific detail @Override protected String getShapeInfo() { return super.getShapeInfo() + " | radius=" + radius; // super.getShapeInfo() OK } // Can access inherited protected fields directly public void changeColor(String newColor) { this.color = newColor; // protected field — accessible in subclass } }
// File: com/devstackflow/shapes/Rectangle.java package com.devstackflow.shapes; import com.devstackflow.base.Shape; public class Rectangle extends Shape { private double width; private double height; public Rectangle(double width, double height, String color, boolean filled) { super(color, filled); // protected constructor this.width = width; this.height = height; } @Override public double area() { return width * height; } @Override public double perimeter() { return 2 * (width + height); } @Override protected String getShapeInfo() { return super.getShapeInfo() + " | " + width + "x" + height; } }
// File: com/devstackflow/demo/ShapeDemo.java package com.devstackflow.demo; import com.devstackflow.shapes.Circle; import com.devstackflow.shapes.Rectangle; import com.devstackflow.base.Shape; public class ShapeDemo { public static void main(String[] args) { Circle circle = new Circle(7.0, "Red", true); Rectangle rect = new Rectangle(5.0, 3.0, "Blue", false); circle.printDetails(); // public — accessible rect.printDetails(); // public — accessible circle.changeColor("Green"); System.out.println("Changed circle color to Green."); circle.printDetails(); // From outside the package hierarchy — protected is NOT accessible: // Shape s = new Circle(5, "Black", false); // s.color = "White"; ← compile error — protected via non-subclass ref // s.getShapeInfo(); ← compile error — protected via non-subclass ref } }
Output:
Shape   : Circle
Info    : Red filled | radius=7.0
Area    : 153.94 sq units
Perim   : 43.98 units

Shape   : Rectangle
Info    : Blue unfilled | 5.0x3.0
Area    : 15.00 sq units
Perim   : 16.00 units

Changed circle color to Green.
Shape   : Circle
Info    : Green filled | radius=7.0
Area    : 153.94 sq units
Perim   : 43.98 units

Circle and Rectangle live in a different package from Shape. They can access color, filled, and getShapeInfo() through inheritance — through this, super, or as their own fields. ShapeDemo in com.devstackflow.demo cannot access those protected members through a Shape reference because ShapeDemo is not a subclass of Shape.

2 — protected Constructor: Subclass-Only Creation

A protected constructor allows subclasses (in any package) to call super(...) while preventing direct instantiation with new ParentClass() from non-subclass code outside the package.

// File: com/devstackflow/event/DomainEvent.java package com.devstackflow.event; import java.time.LocalDateTime; import java.util.UUID; public abstract class DomainEvent { private final String eventId; private final LocalDateTime occurredAt; protected final String aggregateId; // subclasses need it protected final String eventType; // subclasses set it // protected constructor — only subclasses can call super() // Not abstract — has body — just restricted creation protected DomainEvent(String aggregateId, String eventType) { this.eventId = UUID.randomUUID().toString(); this.occurredAt = LocalDateTime.now(); this.aggregateId = aggregateId; this.eventType = eventType; } // public — everyone can read these public String getEventId() { return eventId; } public LocalDateTime getOccurredAt() { return occurredAt; } public String getAggregateId() { return aggregateId; } public String getEventType() { return eventType; } // protected — subclasses can override to add detail protected String getEventDetail() { return "aggregateId=" + aggregateId; } @Override public String toString() { return String.format("[%s] %s | %s | %s", eventType, eventId.substring(0, 8), occurredAt.toLocalTime().withNano(0), getEventDetail()); } }
// File: com/devstackflow/order/events/OrderPlacedEvent.java package com.devstackflow.order.events; // different package from DomainEvent import com.devstackflow.event.DomainEvent; public class OrderPlacedEvent extends DomainEvent { private final String customerId; private final double amount; public OrderPlacedEvent(String orderId, String customerId, double amount) { super(orderId, "ORDER_PLACED"); // protected constructor — via super() this.customerId = customerId; this.amount = amount; } @Override protected String getEventDetail() { // Can reference protected field aggregateId return "orderId=" + aggregateId + " | customer=" + customerId + " | amount=Rs." + amount; } }
// File: com/devstackflow/order/events/OrderCancelledEvent.java package com.devstackflow.order.events; import com.devstackflow.event.DomainEvent; public class OrderCancelledEvent extends DomainEvent { private final String reason; public OrderCancelledEvent(String orderId, String reason) { super(orderId, "ORDER_CANCELLED"); this.reason = reason; } @Override protected String getEventDetail() { return "orderId=" + aggregateId + " | reason=" + reason; } }
// File: com/devstackflow/demo/EventDemo.java package com.devstackflow.demo; import com.devstackflow.order.events.OrderPlacedEvent; import com.devstackflow.order.events.OrderCancelledEvent; import com.devstackflow.event.DomainEvent; public class EventDemo { public static void main(String[] args) { System.out.println("=== Domain Events ===\n"); OrderPlacedEvent placed = new OrderPlacedEvent( "ORD-001", "CUST-101", 2499.0); OrderCancelledEvent cancelled = new OrderCancelledEvent( "ORD-002", "Customer requested"); System.out.println(placed); System.out.println(cancelled); System.out.println("\n=== Public fields from outside ==="); System.out.println("Event ID : " + placed.getEventId()); System.out.println("Aggregate : " + placed.getAggregateId()); System.out.println("Event Type : " + placed.getEventType()); // Cannot do from non-subclass in different package: // new DomainEvent("x", "y"); ← compile error — protected constructor // placed.aggregateId; ← compile error — protected field via non-subclass // placed.getEventDetail(); ← compile error — protected method } }
Output:
=== Domain Events ===

[ORDER_PLACED] a1b2c3d4 | 10:30:00 | orderId=ORD-001 | customer=CUST-101 | amount=Rs.2499.0
[ORDER_CANCELLED] e5f6g7h8 | 10:30:00 | orderId=ORD-002 | reason=Customer requested

=== Public fields from outside ===
Event ID    : a1b2c3d4-...full UUID...
Aggregate   : ORD-001
Event Type  : ORDER_PLACED

DomainEvent cannot be instantiated directly — protected constructor means only subclasses can call super(). EventDemo uses OrderPlacedEvent and OrderCancelledEvent through their public constructors and reads through public getters. The protected fields aggregateId and eventType are accessible inside the event subclasses but not from EventDemo.

3 — The Critical Rule: Qualified Access in Subclasses

The most misunderstood aspect of protected: in a different package, a subclass can access a protected member only through its own type — not through a reference to the parent type or a sibling subclass.

// File: com/devstackflow/base/Account.java package com.devstackflow.base; public class Account { protected double balance = 0.0; // protected protected void credit(double amount) { balance += amount; } }
// File: com/devstackflow/banking/SavingsAccount.java package com.devstackflow.banking; // different package import com.devstackflow.base.Account; public class SavingsAccount extends Account { public void deposit(double amount) { // OK — accessing own inherited protected member (through 'this') credit(amount); // own inherited protected method System.out.println("Balance after deposit: " + balance); // own protected field } public void transfer(SavingsAccount other, double amount) { // OK — 'other' is the SAME subclass type (SavingsAccount) // Java 11+ allows this; protected via subclass-typed reference other.credit(amount); // OK — other is SavingsAccount System.out.println("Transferred Rs." + amount); } public void illegal(Account genericAccount) { // COMPILE ERROR — accessing protected via a plain Account reference // from a different package is NOT allowed // genericAccount.credit(100); ← error // genericAccount.balance; ← error // The reference type is Account, not SavingsAccount System.out.println("Cannot access protected via Account ref from diff package"); } }
// File: com/devstackflow/banking/AccessRuleDemo.java package com.devstackflow.banking; import com.devstackflow.base.Account; public class AccessRuleDemo { public static void main(String[] args) { SavingsAccount acc1 = new SavingsAccount(); SavingsAccount acc2 = new SavingsAccount(); acc1.deposit(5000); acc1.transfer(acc2, 2000); System.out.println("acc1 balance: " + acc1.balance); // OK — SavingsAccount ref System.out.println("acc2 balance: " + acc2.balance); // OK — SavingsAccount ref // From non-subclass code in same package — still blocked: Account base = new Account(); // base.balance; ← compile error even in same package as SavingsAccount // because Account is in a different package // BUT: from inside com.devstackflow.base (same package as Account): // base.balance; would be OK — same package } }
Output:
Balance after deposit: 5000.0
Transferred Rs.2000.0
acc1 balance: 3000.0
acc2 balance: 2000.0

protected vs private vs default vs public — Quick Comparison Table

Aspectprivatedefaultprotectedpublic
Same class✓✓✓✓
Same package, diff class✗✓✓✓
Subclass, same package✗✓✓✓
Subclass, different package✗✗✓✓
Non-subclass, different package✗✗✗✓
Can override when inheritedN/A (not inherited)✓ (same package subclass)✓ (any subclass)✓
Valid on top-level class✗✓✗✓
Valid on member✓✓✓✓
Main use caseEncapsulationPackage-level helpersExtensibility hooksPublic API
Keywordprivate(none)protectedpublic

4 — protected and the Template Method Pattern

protected is the backbone of the Template Method design pattern — one of the most common patterns in Java frameworks. The parent defines the algorithm structure as public final, and protected hooks that subclasses customise.

// File: com/devstackflow/report/ReportGenerator.java package com.devstackflow.report; public abstract class ReportGenerator { // public final — the algorithm structure; subclasses cannot change the order public final String generate(String title) { StringBuilder sb = new StringBuilder(); sb.append(buildHeader(title)); // protected hook sb.append(buildBody()); // protected hook — must implement sb.append(buildFooter()); // protected hook return sb.toString(); } // protected — default header; subclasses may override protected String buildHeader(String title) { return "=".repeat(50) + "\n" + " " + title.toUpperCase() + "\n" + "=".repeat(50) + "\n"; } // protected abstract — subclasses MUST provide the body protected abstract String buildBody(); // protected — default footer; subclasses may override protected String buildFooter() { return "-".repeat(50) + "\nGenerated: " + java.time.LocalDate.now() + "\n"; } }
// File: com/devstackflow/report/SalesReport.java package com.devstackflow.report; import java.util.Map; public class SalesReport extends ReportGenerator { private final Map<String, Double> salesData; public SalesReport(Map<String, Double> salesData) { this.salesData = salesData; } @Override protected String buildBody() { StringBuilder body = new StringBuilder(); body.append(String.format("%-20s %12s%n", "Product", "Revenue")); body.append("-".repeat(34)).append("\n"); double total = 0; for (Map.Entry<String, Double> e : salesData.entrySet()) { body.append(String.format("%-20s Rs.%9.2f%n", e.getKey(), e.getValue())); total += e.getValue(); } body.append("-".repeat(34)).append("\n"); body.append(String.format("%-20s Rs.%9.2f%n", "TOTAL", total)); return body.toString(); } @Override protected String buildFooter() { return super.buildFooter() + "Confidential — Finance Team Only\n"; } }
// File: com/devstackflow/report/AttendanceReport.java package com.devstackflow.report; import java.util.Map; public class AttendanceReport extends ReportGenerator { private final Map<String, Integer> attendanceData; private final int totalDays; public AttendanceReport(Map<String, Integer> attendanceData, int totalDays) { this.attendanceData = attendanceData; this.totalDays = totalDays; } @Override protected String buildBody() { StringBuilder body = new StringBuilder(); body.append(String.format("%-20s %8s %8s%n", "Employee", "Days", "%")); body.append("-".repeat(38)).append("\n"); attendanceData.forEach((name, days) -> { double pct = (days * 100.0) / totalDays; String flag = pct < 75 ? " ← LOW" : ""; body.append(String.format("%-20s %8d %7.1f%%%s%n", name, days, pct, flag)); }); return body.toString(); } }
// File: com/devstackflow/demo/ReportDemo.java package com.devstackflow.demo; import com.devstackflow.report.SalesReport; import com.devstackflow.report.AttendanceReport; import java.util.LinkedHashMap; import java.util.Map; public class ReportDemo { public static void main(String[] args) { // Sales Report Map<String, Double> sales = new LinkedHashMap<>(); sales.put("Laptop", 450000.0); sales.put("Headphones", 89000.0); sales.put("Keyboard", 45000.0); sales.put("Monitor", 180000.0); SalesReport salesReport = new SalesReport(sales); System.out.println(salesReport.generate("Monthly Sales Report — January 2024")); // Attendance Report Map<String, Integer> attendance = new LinkedHashMap<>(); attendance.put("Priya Sharma", 22); attendance.put("Rohan Mehta", 19); attendance.put("Sneha Rao", 16); attendance.put("Karan Singh", 23); AttendanceReport attReport = new AttendanceReport(attendance, 24); System.out.println(attReport.generate("Employee Attendance — January 2024")); } }
Output:
==================================================
  MONTHLY SALES REPORT — JANUARY 2024
==================================================
Product              Revenue
----------------------------------
Laptop               Rs. 450000.00
Headphones           Rs.  89000.00
Keyboard             Rs.  45000.00
Monitor              Rs. 180000.00
----------------------------------
TOTAL                Rs. 764000.00
--------------------------------------------------
Generated: 2024-01-15
Confidential — Finance Team Only

==================================================
  EMPLOYEE ATTENDANCE — JANUARY 2024
==================================================
Employee                 Days       %
--------------------------------------
Priya Sharma               22   91.7%
Rohan Mehta                19   79.2%
Sneha Rao                  16   66.7% ← LOW
Karan Singh                23   95.8%
--------------------------------------------------
Generated: 2024-01-15

SalesReport overrides buildBody() (required) and buildFooter() (optional — calls super.buildFooter() first). AttendanceReport only overrides buildBody() — it uses the default header and footer from the parent. The generate() method in the parent is public final — neither subclass can change the structure. The protected hooks give targeted flexibility without exposing the internals.

Basic Example

class Vehicle { protected String type = "Generic"; // protected — visible in this package and to subclasses anywhere protected void describe() { // protected method System.out.println("Vehicle type: " + type); } } // Imagine this class lives in a DIFFERENT package, e.g. package com.example.cars; // It would still be able to access Vehicle's protected members through inheritance. class Car extends Vehicle { Car() { this.type = "Car"; // accessing inherited protected field } void show() { describe(); // calling inherited protected method } } public class Main { public static void main(String[] args) { Car car = new Car(); car.show(); } }

Output

Vehicle type: Car
  1. ›type and describe() are declared protected in Vehicle — visible to every class in the same package, plus any subclass in any package.
  2. ›Car extends Vehicle, so even if Car lived in a different package (as the comment shows), it could still set this.type and call describe() because it inherits from Vehicle.
  3. ›Car's constructor sets the inherited type field to "Car", and show() calls the inherited describe() method, which prints the updated value.
  4. ›A non-subclass in a different package holding only a Vehicle reference would not be able to access type or describe() — protected access is granted through inheritance, not through an arbitrary parent-type reference.

Best Practices

Use protected for template method hooks, not for sharing state. Protected fields create tight coupling between parent and all subclasses — any change to the field name, type, or semantics must be coordinated across every subclass. Protected methods (especially abstract ones) are cleaner extension points. Prefer exposing a protected method that subclasses override rather than a protected field they read directly.

Always call super.protectedMethod() when overriding, unless you intentionally replace the entire behaviour. When a parent's protected method does work that is still relevant — like base validation, logging, or setup — calling super.method() first in the override preserves that work. Only skip super when the override is designed to completely replace the parent's logic.

Keep the number of protected members small. Every protected member is an implicit contract with every subclass. The more protected members a class has, the harder it becomes to change the parent without reviewing all subclasses. Favour fewer, well-named protected hooks over many protected implementation details.

Document protected members as part of the API for subclass authors. Unlike private (only you) and public (all callers), protected targets a specific audience — developers writing subclasses. Document what protected fields and methods are expected to contain, what invariants they must preserve, and what the expected override contract is.

Common Mistakes

Mistake 1 — Accessing protected via Parent-Type Reference from Different Package

// In com.devstackflow.banking (different package from Account) import com.devstackflow.base.Account; class AuditService { void audit(Account account) { // WRONG — accessing protected via Account reference from outside the package System.out.println(account.balance); // compile error account.credit(100); // compile error } } // Fix — AuditService must be in com.devstackflow.base (same package) // OR Account must make the method public if external callers need it

Mistake 2 — Forgetting to Call super() in Overriding Protected Method

class Logger extends BaseLogger { @Override protected void log(String message) { // Forgot super.log(message) — parent's logging setup is skipped System.out.println("[CHILD] " + message); } } // If parent.log() did important work (timestamp, level, formatting): class Logger extends BaseLogger { @Override protected void log(String message) { super.log(message); // preserve parent's work System.out.println("[CHILD] " + message); // then add own } }

Mistake 3 — Using protected When private Is Sufficient

// WRONG — making a helper protected "just in case" a subclass needs it public class OrderService { protected String buildQuery(String filter) { // exposed to all subclasses return "SELECT * FROM orders WHERE " + filter; } } // CORRECT — if no subclass needs it, keep it private public class OrderService { private String buildQuery(String filter) { // sealed return "SELECT * FROM orders WHERE " + filter; } } // Only widen to protected when a specific subclass need is confirmed

Mistake 4 — Reducing protected Access When Overriding

class Parent { protected void doWork() { System.out.println("Parent work"); } } class Child extends Parent { // @Override // private void doWork() { } // compile error — cannot reduce protected to private @Override protected void doWork() { System.out.println("Child work"); } // same — OK // OR // public void doWork() { } // widening to public — also OK }

Interview Questions

Q1. What does the protected access modifier provide in Java?

protected provides two layers of access. First, the same layer as default — all classes within the same package can access the member. Second, an additional layer — subclasses in any package can access the member through inheritance: through super, through this, or through a reference typed as the subclass. The member is not accessible to non-subclass code in a different package, even if that code holds a reference to the parent type.

Q2. What is the difference between protected and default (package-private)?

Both protected and default allow access within the same package. The sole difference is inheritance across packages: protected additionally allows subclasses in different packages to access the member, while default does not. A default method in package A is invisible to a subclass in package B. A protected method in package A is accessible to subclasses in package B through the subclass reference. For all intra-package access, they behave identically.

Q3. Can a subclass in a different package access a protected member through a parent-type reference?

No — this is the critical rule that catches most developers. In a different package, a subclass can access the protected member only through its own type (via this, super, or a reference typed as the subclass). Accessing it through a plain parent-type reference — Account account = ...; account.balance — from a different package is a compile error even inside a subclass. The restriction is: you must prove you are accessing it through the inheritance chain, not as an arbitrary external caller who happens to hold a parent reference.

Q4. Can protected be used on a top-level class?

No. Only public and default (no modifier) are valid on top-level classes. protected and private cause a compile error on top-level class declarations. protected is valid on inner classes, nested static classes, and all class members — fields, methods, and constructors.

Q5. How does protected support the Template Method design pattern?

In the Template Method pattern, the parent class declares the algorithm structure in a public final method that calls several protected hook methods. Subclasses override the protected hooks to supply their specific behaviour without changing the algorithm's structure. The protected modifier is essential because the hooks need to be accessible from subclasses in any package, but they should not be part of the public API — callers invoke the final template method, not the individual hooks.

Q6. What happens when you override a protected method in a subclass?

An overriding method must maintain or widen the visibility — never narrow it. A protected method can be overridden as protected (same) or public (wider). It cannot be overridden as default or private — the compiler rejects this because it would break the Liskov Substitution Principle: callers that could invoke the protected method through a parent reference (in the same package) would no longer be able to call the child version.

FAQs

Can a protected constructor be called from a non-subclass in the same package?

Yes. Within the same package, protected behaves exactly like default — any class in the package can call the protected constructor with new ParentClass(args). The cross-package restriction only applies to different packages, where only subclasses can call super(args). From within the declaring package, protected provides full package-level access.

Is protected access granted to an interface that extends another interface?

No. Interfaces only deal with public members. An interface extending another interface inherits its abstract methods, all of which are implicitly public. protected and default are not valid on interface abstract methods. protected is meaningful only in class hierarchies, not interface hierarchies.

Why do some frameworks mark fields protected instead of private?

Frameworks like Spring and Hibernate often extend classes reflectively or generate subclasses at runtime (proxies). protected fields are accessible from these generated subclasses, while private fields would require reflection with setAccessible(true). The tradeoff is that protected fields are less tightly encapsulated — anyone writing a subclass can access them directly. This is one of the reasons the Spring community recommends using the protected modifier on @Autowired fields instead of private when using constructor injection is not possible.

Can you have a protected field with no getter?

Yes — within the class and its subclasses, the field is accessed directly without a getter. This is common in abstract base classes where the protected field is expected to be read and written by subclasses as part of the implementation. It is generally better practice to expose even protected fields through protected getters and setters — this gives the parent class control over what happens when subclasses read or change the value.

Does protected affect the class loader or module system?

In the Java module system (Java 9+), protected still works as described, but only if the package is exports in the module-info.java. If a module does not export the package containing the protected member, no outside code — including subclasses in other modules — can access it, regardless of the protected modifier. The module system adds a layer of encapsulation above protected. Within a single unnamed module (the traditional classpath), protected behaves exactly as described throughout this article.

Summary

protected is the modifier for inheritance-based extensibility. It grants the same package-level access as default, plus one more group: subclasses in any package. Subclasses access protected members through inheritance — this, super, or a subclass-typed reference — never through an arbitrary parent-type reference from a different package.

Its primary use cases are template method hooks, inherited state that subclasses need to read or modify, and protected constructors that allow subclass instantiation while preventing arbitrary direct creation. In all these cases, protected provides targeted access — wider than private (which shuts out subclasses), narrower than public (which exposes everything to everyone).

The discipline for protected: only make a member protected when you have an identified subclass that needs it. Every protected member is a commitment to every current and future subclass author. Keep the set of protected members minimal, document them clearly, and prefer protected methods over protected fields wherever possible.

What to Read Next