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.
1// File: com/devstackflow/base/Shape.java
2package com.devstackflow.base;
3
4public abstract class Shape {
5
6 // protected — subclasses in any package can read and write
7 protected String color;
8 protected boolean filled;
9
10 // protected constructor — subclasses call super(...)
11 protected Shape(String color, boolean filled) {
12 this.color = color;
13 this.filled = filled;
14 }
15
16 // protected method — subclasses can override this hook
17 protected String getShapeInfo() {
18 return color + (filled ? " filled" : " unfilled");
19 }
20
21 // abstract — subclasses must provide implementation
22 public abstract double area();
23 public abstract double perimeter();
24
25 // public final — concrete template method using protected hook
26 public final void printDetails() {
27 System.out.printf("Shape : %s%n", getClass().getSimpleName());
28 System.out.printf("Info : %s%n", getShapeInfo());
29 System.out.printf("Area : %.2f sq units%n", area());
30 System.out.printf("Perim : %.2f units%n%n", perimeter());
31 }
32}1// File: com/devstackflow/shapes/Circle.java
2package com.devstackflow.shapes; // DIFFERENT package from Shape
3
4import com.devstackflow.base.Shape;
5
6public class Circle extends Shape {
7
8 private double radius;
9
10 public Circle(double radius, String color, boolean filled) {
11 super(color, filled); // protected constructor — accessible via super()
12 this.radius = radius;
13 }
14
15 @Override
16 public double area() {
17 return Math.PI * radius * radius;
18 }
19
20 @Override
21 public double perimeter() {
22 return 2 * Math.PI * radius;
23 }
24
25 // Overrides protected method — adds circle-specific detail
26 @Override
27 protected String getShapeInfo() {
28 return super.getShapeInfo() + " | radius=" + radius; // super.getShapeInfo() OK
29 }
30
31 // Can access inherited protected fields directly
32 public void changeColor(String newColor) {
33 this.color = newColor; // protected field — accessible in subclass
34 }
35}1// File: com/devstackflow/shapes/Rectangle.java
2package com.devstackflow.shapes;
3
4import com.devstackflow.base.Shape;
5
6public class Rectangle extends Shape {
7
8 private double width;
9 private double height;
10
11 public Rectangle(double width, double height, String color, boolean filled) {
12 super(color, filled); // protected constructor
13 this.width = width;
14 this.height = height;
15 }
16
17 @Override
18 public double area() {
19 return width * height;
20 }
21
22 @Override
23 public double perimeter() {
24 return 2 * (width + height);
25 }
26
27 @Override
28 protected String getShapeInfo() {
29 return super.getShapeInfo() + " | " + width + "x" + height;
30 }
31}1// File: com/devstackflow/demo/ShapeDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.shapes.Circle;
5import com.devstackflow.shapes.Rectangle;
6import com.devstackflow.base.Shape;
7
8public class ShapeDemo {
9
10 public static void main(String[] args) {
11
12 Circle circle = new Circle(7.0, "Red", true);
13 Rectangle rect = new Rectangle(5.0, 3.0, "Blue", false);
14
15 circle.printDetails(); // public — accessible
16 rect.printDetails(); // public — accessible
17
18 circle.changeColor("Green");
19 System.out.println("Changed circle color to Green.");
20 circle.printDetails();
21
22 // From outside the package hierarchy — protected is NOT accessible:
23 // Shape s = new Circle(5, "Black", false);
24 // s.color = "White"; ← compile error — protected via non-subclass ref
25 // s.getShapeInfo(); ← compile error — protected via non-subclass ref
26 }
27}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.
1// File: com/devstackflow/event/DomainEvent.java
2package com.devstackflow.event;
3
4import java.time.LocalDateTime;
5import java.util.UUID;
6
7public abstract class DomainEvent {
8
9 private final String eventId;
10 private final LocalDateTime occurredAt;
11 protected final String aggregateId; // subclasses need it
12 protected final String eventType; // subclasses set it
13
14 // protected constructor — only subclasses can call super()
15 // Not abstract — has body — just restricted creation
16 protected DomainEvent(String aggregateId, String eventType) {
17 this.eventId = UUID.randomUUID().toString();
18 this.occurredAt = LocalDateTime.now();
19 this.aggregateId = aggregateId;
20 this.eventType = eventType;
21 }
22
23 // public — everyone can read these
24 public String getEventId() { return eventId; }
25 public LocalDateTime getOccurredAt() { return occurredAt; }
26 public String getAggregateId() { return aggregateId; }
27 public String getEventType() { return eventType; }
28
29 // protected — subclasses can override to add detail
30 protected String getEventDetail() {
31 return "aggregateId=" + aggregateId;
32 }
33
34 @Override
35 public String toString() {
36 return String.format("[%s] %s | %s | %s",
37 eventType, eventId.substring(0, 8),
38 occurredAt.toLocalTime().withNano(0),
39 getEventDetail());
40 }
41}1// File: com/devstackflow/order/events/OrderPlacedEvent.java
2package com.devstackflow.order.events; // different package from DomainEvent
3
4import com.devstackflow.event.DomainEvent;
5
6public class OrderPlacedEvent extends DomainEvent {
7
8 private final String customerId;
9 private final double amount;
10
11 public OrderPlacedEvent(String orderId, String customerId, double amount) {
12 super(orderId, "ORDER_PLACED"); // protected constructor — via super()
13 this.customerId = customerId;
14 this.amount = amount;
15 }
16
17 @Override
18 protected String getEventDetail() {
19 // Can reference protected field aggregateId
20 return "orderId=" + aggregateId
21 + " | customer=" + customerId
22 + " | amount=Rs." + amount;
23 }
24}1// File: com/devstackflow/order/events/OrderCancelledEvent.java
2package com.devstackflow.order.events;
3
4import com.devstackflow.event.DomainEvent;
5
6public class OrderCancelledEvent extends DomainEvent {
7
8 private final String reason;
9
10 public OrderCancelledEvent(String orderId, String reason) {
11 super(orderId, "ORDER_CANCELLED");
12 this.reason = reason;
13 }
14
15 @Override
16 protected String getEventDetail() {
17 return "orderId=" + aggregateId + " | reason=" + reason;
18 }
19}1// File: com/devstackflow/demo/EventDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.order.events.OrderPlacedEvent;
5import com.devstackflow.order.events.OrderCancelledEvent;
6import com.devstackflow.event.DomainEvent;
7
8public class EventDemo {
9
10 public static void main(String[] args) {
11
12 System.out.println("=== Domain Events ===\n");
13
14 OrderPlacedEvent placed = new OrderPlacedEvent(
15 "ORD-001", "CUST-101", 2499.0);
16 OrderCancelledEvent cancelled = new OrderCancelledEvent(
17 "ORD-002", "Customer requested");
18
19 System.out.println(placed);
20 System.out.println(cancelled);
21
22 System.out.println("\n=== Public fields from outside ===");
23 System.out.println("Event ID : " + placed.getEventId());
24 System.out.println("Aggregate : " + placed.getAggregateId());
25 System.out.println("Event Type : " + placed.getEventType());
26
27 // Cannot do from non-subclass in different package:
28 // new DomainEvent("x", "y"); ← compile error — protected constructor
29 // placed.aggregateId; ← compile error — protected field via non-subclass
30 // placed.getEventDetail(); ← compile error — protected method
31 }
32}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.
1// File: com/devstackflow/base/Account.java
2package com.devstackflow.base;
3
4public class Account {
5 protected double balance = 0.0; // protected
6
7 protected void credit(double amount) {
8 balance += amount;
9 }
10}1// File: com/devstackflow/banking/SavingsAccount.java
2package com.devstackflow.banking; // different package
3
4import com.devstackflow.base.Account;
5
6public class SavingsAccount extends Account {
7
8 public void deposit(double amount) {
9 // OK — accessing own inherited protected member (through 'this')
10 credit(amount); // own inherited protected method
11 System.out.println("Balance after deposit: " + balance); // own protected field
12 }
13
14 public void transfer(SavingsAccount other, double amount) {
15 // OK — 'other' is the SAME subclass type (SavingsAccount)
16 // Java 11+ allows this; protected via subclass-typed reference
17 other.credit(amount); // OK — other is SavingsAccount
18 System.out.println("Transferred Rs." + amount);
19 }
20
21 public void illegal(Account genericAccount) {
22 // COMPILE ERROR — accessing protected via a plain Account reference
23 // from a different package is NOT allowed
24 // genericAccount.credit(100); ← error
25 // genericAccount.balance; ← error
26 // The reference type is Account, not SavingsAccount
27 System.out.println("Cannot access protected via Account ref from diff package");
28 }
29}1// File: com/devstackflow/banking/AccessRuleDemo.java
2package com.devstackflow.banking;
3
4import com.devstackflow.base.Account;
5
6public class AccessRuleDemo {
7
8 public static void main(String[] args) {
9
10 SavingsAccount acc1 = new SavingsAccount();
11 SavingsAccount acc2 = new SavingsAccount();
12
13 acc1.deposit(5000);
14 acc1.transfer(acc2, 2000);
15
16 System.out.println("acc1 balance: " + acc1.balance); // OK — SavingsAccount ref
17 System.out.println("acc2 balance: " + acc2.balance); // OK — SavingsAccount ref
18
19 // From non-subclass code in same package — still blocked:
20 Account base = new Account();
21 // base.balance; ← compile error even in same package as SavingsAccount
22 // because Account is in a different package
23
24 // BUT: from inside com.devstackflow.base (same package as Account):
25 // base.balance; would be OK — same package
26 }
27}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
| Aspect | private | default | protected | public |
|---|---|---|---|---|
| Same class | ✓ | ✓ | ✓ | ✓ |
| Same package, diff class | ✗ | ✓ | ✓ | ✓ |
| Subclass, same package | ✗ | ✓ | ✓ | ✓ |
| Subclass, different package | ✗ | ✗ | ✓ | ✓ |
| Non-subclass, different package | ✗ | ✗ | ✗ | ✓ |
| Can override when inherited | N/A (not inherited) | ✓ (same package subclass) | ✓ (any subclass) | ✓ |
| Valid on top-level class | ✗ | ✓ | ✗ | ✓ |
| Valid on member | ✓ | ✓ | ✓ | ✓ |
| Main use case | Encapsulation | Package-level helpers | Extensibility hooks | Public API |
| Keyword | private | (none) | protected | public |
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.
1// File: com/devstackflow/report/ReportGenerator.java
2package com.devstackflow.report;
3
4public abstract class ReportGenerator {
5
6 // public final — the algorithm structure; subclasses cannot change the order
7 public final String generate(String title) {
8 StringBuilder sb = new StringBuilder();
9 sb.append(buildHeader(title)); // protected hook
10 sb.append(buildBody()); // protected hook — must implement
11 sb.append(buildFooter()); // protected hook
12 return sb.toString();
13 }
14
15 // protected — default header; subclasses may override
16 protected String buildHeader(String title) {
17 return "=".repeat(50) + "\n" + " " + title.toUpperCase() + "\n"
18 + "=".repeat(50) + "\n";
19 }
20
21 // protected abstract — subclasses MUST provide the body
22 protected abstract String buildBody();
23
24 // protected — default footer; subclasses may override
25 protected String buildFooter() {
26 return "-".repeat(50) + "\nGenerated: "
27 + java.time.LocalDate.now() + "\n";
28 }
29}1// File: com/devstackflow/report/SalesReport.java
2package com.devstackflow.report;
3
4import java.util.Map;
5
6public class SalesReport extends ReportGenerator {
7
8 private final Map<String, Double> salesData;
9
10 public SalesReport(Map<String, Double> salesData) {
11 this.salesData = salesData;
12 }
13
14 @Override
15 protected String buildBody() {
16 StringBuilder body = new StringBuilder();
17 body.append(String.format("%-20s %12s%n", "Product", "Revenue"));
18 body.append("-".repeat(34)).append("\n");
19 double total = 0;
20 for (Map.Entry<String, Double> e : salesData.entrySet()) {
21 body.append(String.format("%-20s Rs.%9.2f%n", e.getKey(), e.getValue()));
22 total += e.getValue();
23 }
24 body.append("-".repeat(34)).append("\n");
25 body.append(String.format("%-20s Rs.%9.2f%n", "TOTAL", total));
26 return body.toString();
27 }
28
29 @Override
30 protected String buildFooter() {
31 return super.buildFooter() + "Confidential — Finance Team Only\n";
32 }
33}1// File: com/devstackflow/report/AttendanceReport.java
2package com.devstackflow.report;
3
4import java.util.Map;
5
6public class AttendanceReport extends ReportGenerator {
7
8 private final Map<String, Integer> attendanceData;
9 private final int totalDays;
10
11 public AttendanceReport(Map<String, Integer> attendanceData, int totalDays) {
12 this.attendanceData = attendanceData;
13 this.totalDays = totalDays;
14 }
15
16 @Override
17 protected String buildBody() {
18 StringBuilder body = new StringBuilder();
19 body.append(String.format("%-20s %8s %8s%n", "Employee", "Days", "%"));
20 body.append("-".repeat(38)).append("\n");
21 attendanceData.forEach((name, days) -> {
22 double pct = (days * 100.0) / totalDays;
23 String flag = pct < 75 ? " ← LOW" : "";
24 body.append(String.format("%-20s %8d %7.1f%%%s%n",
25 name, days, pct, flag));
26 });
27 return body.toString();
28 }
29}1// File: com/devstackflow/demo/ReportDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.report.SalesReport;
5import com.devstackflow.report.AttendanceReport;
6
7import java.util.LinkedHashMap;
8import java.util.Map;
9
10public class ReportDemo {
11
12 public static void main(String[] args) {
13
14 // Sales Report
15 Map<String, Double> sales = new LinkedHashMap<>();
16 sales.put("Laptop", 450000.0);
17 sales.put("Headphones", 89000.0);
18 sales.put("Keyboard", 45000.0);
19 sales.put("Monitor", 180000.0);
20
21 SalesReport salesReport = new SalesReport(sales);
22 System.out.println(salesReport.generate("Monthly Sales Report — January 2024"));
23
24 // Attendance Report
25 Map<String, Integer> attendance = new LinkedHashMap<>();
26 attendance.put("Priya Sharma", 22);
27 attendance.put("Rohan Mehta", 19);
28 attendance.put("Sneha Rao", 16);
29 attendance.put("Karan Singh", 23);
30
31 AttendanceReport attReport = new AttendanceReport(attendance, 24);
32 System.out.println(attReport.generate("Employee Attendance — January 2024"));
33 }
34}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.
Real-World Example — Notification Template Engine
The Business Problem
A notification system at a company like Swiggy sends order updates via multiple channels — SMS, email, and push notification. Each channel formats the message differently but all share the same core data validation and logging logic. Protected methods provide the shared infrastructure; subclasses supply channel-specific formatting.
1// File: com/swiggy/notify/base/NotificationSender.java
2package com.swiggy.notify.base;
3
4import java.time.LocalDateTime;
5
6public abstract class NotificationSender {
7
8 protected final String senderName;
9
10 protected NotificationSender(String senderName) {
11 this.senderName = senderName;
12 }
13
14 // public final — controls the send workflow for all channels
15 public final boolean send(String recipientId, String orderId,
16 String message) {
17 if (!validateRecipient(recipientId)) { // protected
18 log("INVALID recipient: " + recipientId); // protected
19 return false;
20 }
21 String formatted = formatMessage(orderId, message); // protected abstract
22 boolean sent = doSend(recipientId, formatted); // protected abstract
23 log((sent ? "SENT" : "FAILED") + " to " + recipientId); // protected
24 return sent;
25 }
26
27 // protected — subclasses can override validation logic
28 protected boolean validateRecipient(String recipientId) {
29 return recipientId != null && !recipientId.isBlank();
30 }
31
32 // protected abstract — each channel formats differently
33 protected abstract String formatMessage(String orderId, String message);
34
35 // protected abstract — each channel sends differently
36 protected abstract boolean doSend(String recipientId, String formatted);
37
38 // protected — subclasses can override log format
39 protected void log(String entry) {
40 System.out.printf(" [%s][%s] %s: %s%n",
41 LocalDateTime.now().toLocalTime().withNano(0),
42 senderName, getClass().getSimpleName(), entry);
43 }
44}1// File: com/swiggy/notify/channels/SmsSender.java
2package com.swiggy.notify.channels;
3
4import com.swiggy.notify.base.NotificationSender;
5
6public class SmsSender extends NotificationSender {
7
8 private static final int SMS_MAX_CHARS = 160;
9
10 public SmsSender() {
11 super("SMS-GW"); // protected constructor
12 }
13
14 @Override
15 protected boolean validateRecipient(String phone) {
16 // stricter validation — must be 10-digit Indian number
17 return super.validateRecipient(phone)
18 && phone.matches("[6-9]\\d{9}");
19 }
20
21 @Override
22 protected String formatMessage(String orderId, String message) {
23 String raw = "[Swiggy] Order " + orderId + ": " + message;
24 // Truncate to 160 chars for SMS
25 return raw.length() > SMS_MAX_CHARS
26 ? raw.substring(0, SMS_MAX_CHARS - 3) + "..."
27 : raw;
28 }
29
30 @Override
31 protected boolean doSend(String phone, String message) {
32 System.out.println(" SMS → " + phone + ": " + message);
33 return true; // simulate success
34 }
35}1// File: com/swiggy/notify/channels/EmailSender.java
2package com.swiggy.notify.channels;
3
4import com.swiggy.notify.base.NotificationSender;
5
6public class EmailSender extends NotificationSender {
7
8 public EmailSender() {
9 super("EMAIL-GW"); // protected constructor
10 }
11
12 @Override
13 protected boolean validateRecipient(String email) {
14 return super.validateRecipient(email)
15 && email.contains("@")
16 && email.contains(".");
17 }
18
19 @Override
20 protected String formatMessage(String orderId, String message) {
21 return "Subject: Your Swiggy Order Update — " + orderId + "\n\n"
22 + "Dear Customer,\n\n"
23 + message + "\n\n"
24 + "Thank you for ordering with Swiggy!\n"
25 + "— The Swiggy Team";
26 }
27
28 @Override
29 protected boolean doSend(String email, String message) {
30 System.out.println(" EMAIL → " + email);
31 System.out.println(message.replace("\n", "\n "));
32 return true;
33 }
34}1// File: com/swiggy/notify/channels/PushSender.java
2package com.swiggy.notify.channels;
3
4import com.swiggy.notify.base.NotificationSender;
5
6public class PushSender extends NotificationSender {
7
8 public PushSender() {
9 super("PUSH-GW"); // protected constructor
10 }
11
12 @Override
13 protected String formatMessage(String orderId, String message) {
14 return "🚀 " + message + " [#" + orderId + "]";
15 }
16
17 @Override
18 protected boolean doSend(String deviceToken, String message) {
19 System.out.println(" PUSH → device:" + deviceToken + " | " + message);
20 return !deviceToken.equals("INVALID_TOKEN"); // simulate failure on bad token
21 }
22}1// File: com/swiggy/NotificationDemo.java
2package com.swiggy;
3
4import com.swiggy.notify.channels.EmailSender;
5import com.swiggy.notify.channels.PushSender;
6import com.swiggy.notify.channels.SmsSender;
7
8public class NotificationDemo {
9
10 public static void main(String[] args) {
11
12 System.out.println("╔══════════════════════════════════════════╗");
13 System.out.println("║ SWIGGY NOTIFICATION ENGINE DEMO ║");
14 System.out.println("╚══════════════════════════════════════════╝\n");
15
16 SmsSender sms = new SmsSender();
17 EmailSender email = new EmailSender();
18 PushSender push = new PushSender();
19
20 System.out.println("=== SMS Notifications ===");
21 sms.send("9876543210", "ORD-001", "Your order is out for delivery!");
22 sms.send("1234", "ORD-002", "Payment confirmed."); // invalid phone
23 sms.send("", "ORD-003", "Rider assigned."); // blank recipient
24
25 System.out.println("\n=== Email Notifications ===");
26 email.send("priya@swiggy.com", "ORD-001", "Your order has been delivered.");
27 email.send("invalid-email", "ORD-004", "Order placed.");
28
29 System.out.println("\n=== Push Notifications ===");
30 push.send("device_abc123", "ORD-001", "Your rider is nearby!");
31 push.send("INVALID_TOKEN", "ORD-005", "Order dispatched.");
32 push.send("device_def456", "ORD-006", "Delivery completed. Rate your experience!");
33 }
34}Output:
╔══════════════════════════════════════════╗
║ SWIGGY NOTIFICATION ENGINE DEMO ║
╚══════════════════════════════════════════╝
=== SMS Notifications ===
SMS → 9876543210: [Swiggy] Order ORD-001: Your order is out for delivery!
[10:30:00][SMS-GW] SmsSender: SENT to 9876543210
[10:30:00][SMS-GW] SmsSender: INVALID recipient: 1234
[10:30:00][SMS-GW] SmsSender: INVALID recipient:
=== Email Notifications ===
EMAIL → priya@swiggy.com
Subject: Your Swiggy Order Update — ORD-001
Dear Customer,
Your order has been delivered.
Thank you for ordering with Swiggy!
— The Swiggy Team
[10:30:00][EMAIL-GW] EmailSender: SENT to priya@swiggy.com
[10:30:00][EMAIL-GW] EmailSender: INVALID recipient: invalid-email
=== Push Notifications ===
PUSH → device:device_abc123 | 🚀 Your rider is nearby! [#ORD-001]
[10:30:00][PUSH-GW] PushSender: SENT to device_abc123
PUSH → device:INVALID_TOKEN | 🚀 Order dispatched. [#ORD-005]
[10:30:00][PUSH-GW] PushSender: FAILED to INVALID_TOKEN
PUSH → device:device_def456 | 🚀 Delivery completed. Rate your experience! [#ORD-006]
[10:30:00][PUSH-GW] PushSender: SENT to device_def456
Every channel extends NotificationSender from a different package and accesses the protected constructor and senderName field. Each overrides the protected abstract methods for its channel-specific logic. SmsSender also overrides validateRecipient() — calling super.validateRecipient() first then adding phone-number validation. The public send() method in the parent controls the full workflow — channels can only customise through their protected hooks.
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
1// In com.devstackflow.banking (different package from Account)
2import com.devstackflow.base.Account;
3
4class AuditService {
5 void audit(Account account) {
6 // WRONG — accessing protected via Account reference from outside the package
7 System.out.println(account.balance); // compile error
8 account.credit(100); // compile error
9 }
10}
11
12// Fix — AuditService must be in com.devstackflow.base (same package)
13// OR Account must make the method public if external callers need itMistake 2 — Forgetting to Call super() in Overriding Protected Method
1class Logger extends BaseLogger {
2 @Override
3 protected void log(String message) {
4 // Forgot super.log(message) — parent's logging setup is skipped
5 System.out.println("[CHILD] " + message);
6 }
7}
8
9// If parent.log() did important work (timestamp, level, formatting):
10class Logger extends BaseLogger {
11 @Override
12 protected void log(String message) {
13 super.log(message); // preserve parent's work
14 System.out.println("[CHILD] " + message); // then add own
15 }
16}Mistake 3 — Using protected When private Is Sufficient
1// WRONG — making a helper protected "just in case" a subclass needs it
2public class OrderService {
3 protected String buildQuery(String filter) { // exposed to all subclasses
4 return "SELECT * FROM orders WHERE " + filter;
5 }
6}
7
8// CORRECT — if no subclass needs it, keep it private
9public class OrderService {
10 private String buildQuery(String filter) { // sealed
11 return "SELECT * FROM orders WHERE " + filter;
12 }
13}
14// Only widen to protected when a specific subclass need is confirmedMistake 4 — Reducing protected Access When Overriding
1class Parent {
2 protected void doWork() { System.out.println("Parent work"); }
3}
4
5class Child extends Parent {
6 // @Override
7 // private void doWork() { } // compile error — cannot reduce protected to private
8
9 @Override
10 protected void doWork() { System.out.println("Child work"); } // same — OK
11 // OR
12 // public void doWork() { } // widening to public — also OK
13}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
Learn what happens when you don't specify an access modifier.