Definition
Yes, you can change the access modifier when overriding, but only to widen it (make more accessible).
- Access modifier order (narrow to wide):
private->default(package-private) ->protected->public. - Widening is valid:
protectedin parent can becomepublicin child. - Narrowing is invalid:
publicin parent cannot becomeprivateorprotectedin child. - Private methods are not inherited. The child does not see them. If the child defines a same-name method, it is a new method, not an override.
- Static methods are class-level. They bind at compile time to the variable type. A child can define a same-name static method, but this is method hiding, not overriding.
- Final methods are sealed. The compiler prevents any child from overriding them.
Head office at National Bank of India publishes a procedure visible to all branches (protected). Mumbai branch can make it visible to everyone (public): more people can see it, which is fine. But Mumbai branch cannot make it visible to only the branch manager (private): that would break branches that already follow the procedure. If head office marks a procedure final, no branch can modify it. If head office keeps a procedure private, branches do not even know it exists.
Understanding the Concepts
Can you change the access scope of an overridden method?
Yes, but only to widen it. The Liskov Substitution Principle requires that code written against the parent still works when the child is substituted.
What is the access modifier hierarchy?
The hierarchy from most restrictive to least restrictive is: private, default (no keyword), protected, public. When overriding, you can move right (widen) but never left (narrow).
// Access modifier order (narrow to wide):
// private -> default (package-private) -> protected -> public
// <--- CANNOT move this direction when overriding ---
// ---> CAN move this direction when overriding --->
What are the valid and invalid combinations?
package com.bank.core;
// Parent: default (package-private)
class Account {
void processInternal() { }
}
class SavingsAccount extends Account {
@Override void processInternal() { } // same (default -> default): valid
// @Override protected void processInternal() { } // wider (default -> protected): valid
// @Override public void processInternal() { } // wider (default -> public): valid
// @Override private void processInternal() { } // narrower: compile error
}
// Parent: protected
class BaseAccount {
protected void validate() { }
}
class CheckingAccount extends BaseAccount {
@Override protected void validate() { } // same: valid
@Override public void validate() { } // wider (protected -> public): valid
// @Override private void validate() { } // narrower: compile error
}
// Parent: public
class OpenAccount {
public void deposit(java.math.BigDecimal amount) { }
}
class ChildAccount extends OpenAccount {
@Override public void deposit(java.math.BigDecimal amount) { } // same (only option)
// public is the widest. Can only stay public.
}
Why is widening allowed but narrowing not?
The Liskov Substitution Principle requires that a child object can replace its parent without breaking existing code.
// Code written against the parent:
public void processAccount(Account account) {
account.validate(); // protected: accessible from same package or subclass
}
// If child narrows validate() to private:
// processAccount(new SavingsAccount());
// account.validate() -> fails. Cannot access private method through Account reference.
// The child broke code that worked with the parent. Liskov violated.
// If child widens validate() to public:
// account.validate() -> works. Public is accessible from everywhere.
// The child is more accessible. Callers are happy. Liskov satisfied.
Why can private methods not be overridden?
Private methods are not inherited. The child does not see them. If the child defines a method with the same name, it is a completely new method.
class Account {
private void audit() { System.out.println("Account audit"); }
}
class SavingsAccount extends Account {
private void audit() { System.out.println("Savings audit"); }
// This is a NEW method, not an override.
// @Override private void audit() { } // compile error: "does not override"
}
Why are static methods hidden, not overridden?
Static methods are resolved at compile time based on the variable type (reference type), not the actual object type. There is no dynamic dispatch for static methods.
class Account {
public static String getPolicy() { return "Standard Policy"; }
}
class SavingsAccount extends Account {
public static String getPolicy() { return "Savings Policy"; } // hiding, not overriding
}
// Proof:
Account ref = new SavingsAccount("SAV001", java.math.BigDecimal.ZERO);
System.out.println(ref.getPolicy());
// Output: "Standard Policy" (Account's version, NOT SavingsAccount's)
// The compiler sees ref is Account -> binds to Account.getPolicy().
// No dynamic dispatch. Static binding.
Why are final methods sealed?
Final methods guarantee behavior that must never change. The compiler prevents any child from overriding them.
class Account {
public final java.math.BigDecimal getBalance() { return balance; }
// Final: no child can change this. Security critical.
}
// class SavingsAccount extends Account {
// @Override public BigDecimal getBalance() { return BigDecimal.ZERO; }
// // compile error: "cannot override final method from Account"
// }
What are common mistakes?
The most common mistake is expecting polymorphism from static methods.
// WRONG expectation: Account ref = new SavingsAccount();
// ref.getPolicy() returns "Savings Policy" (NO! Returns "Standard Policy")
// Static methods bind at compile time to the reference type.
// FIX: never call static methods on instance variables.
// Use: Account.getPolicy() or SavingsAccount.getPolicy() to be explicit.
What should a senior engineer know?
- The JVM can inline final methods for performance because it knows no override exists.
- IntelliJ and SonarQube warn when static methods are called on instance references because it misleads developers about the binding mechanism.
- In records (Java 16), all accessor methods are implicitly final because records are immutable data carriers.
Visual Overview
ACCESS MODIFIER HIERARCHY ───────────────────────────────────────── private -> default -> protected -> public (most (least restrictive) restrictive) Override rule: ──────────────────────────────────────→ CAN widen (move right) ←────────────────────────────────────── CANNOT narrow (move left) CANNOT OVERRIDE: ┌──────────┐ ┌──────────┐ ┌──────────┐ │ private │ │ static │ │ final │ │ not │ │ hidden │ │ sealed │ │ inherited │ │ compile │ │ no │ │ │ │ time │ │ override │ └──────────┘ └──────────┘ └──────────┘
Key Takeaway
Yes, you can change the access modifier when overriding, but only to widen it. The order from narrow to wide: private, default, protected, public. You can move right (widen) but never left (narrow). If the parent method is protected, the child can make it protected (same) or public (wider), but not default or private (narrower). If the parent method is public, the child must keep it public because public is already the widest. This rule exists because of the Liskov Substitution Principle: code written against the parent expects a certain accessibility level, and the child must maintain at least that level.
Three types of methods cannot be overridden. Private methods are not inherited: the child does not even know they exist. Static methods are class-level: they bind at compile time to the variable type, not the object type. A child can define a same-name static method, but this is method hiding, not overriding. Final methods are explicitly sealed: the compiler prevents any child from changing their behavior.
Remember one sentence: you can widen access when overriding (protected to public) but never narrow it, and private, static, and final methods cannot be overridden at all.
---