Definition
Four levels from most open to most restricted:
- public – accessible from any class in any package. Use for API methods and service interfaces.
- protected – accessible within the same package, and by subclasses in other packages. Use sparingly for inheritance.
- default (package-private) – no keyword written. Accessible only within the same package. Use for internal implementation classes.
- private – accessible only within the declaring class. Not visible to subclasses or other classes. Use for all fields and internal methods.
A bank building with four security levels. Public areas (lobby) are open to everyone. Protected areas (back office) are accessible to employees of this branch and managers from other branches (subclasses). Package-private areas (operations room) are only for employees of this specific branch. Private areas (the vault) are accessible only to the vault officer (the class itself).
Understanding the Concepts
What is the visibility of each modifier?
| Modifier | Same class | Same package | Subclass (other package) | Other package |
|---|---|---|---|---|
| public | Yes | Yes | Yes | Yes |
| protected | Yes | Yes | Yes | No |
| default (none) | Yes | Yes | No | No |
| private | Yes | No | No | No |
How does each modifier work in code?
package com.bank.core;
public class Account {
public String accountId; // visible everywhere
protected String holderName; // visible in package + subclasses
String branchCode; // package-private (no keyword)
private BigDecimal balance; // visible only inside Account
public BigDecimal getBalance() { // public getter
return balance;
}
private void validateBalance() { // only Account can call this
if (balance.signum() < 0) {
throw new IllegalStateException("Negative balance");
}
}
}
package com.bank.core; // SAME package
public class TransferService {
public void transfer(Account from, Account to, BigDecimal amount) {
// public field: accessible
System.out.println(from.accountId);
// protected field: accessible (same package)
System.out.println(from.holderName);
// package-private field: accessible (same package)
System.out.println(from.branchCode);
// private field: NOT accessible
// System.out.println(from.balance); // COMPILE ERROR
System.out.println(from.getBalance()); // use public getter
// private method: NOT accessible
// from.validateBalance(); // COMPILE ERROR
}
}
package com.bank.mobile; // DIFFERENT package
import com.bank.core.Account;
public class MobileApp {
public void display(Account account) {
// public: accessible
System.out.println(account.accountId);
// protected: NOT accessible (different package, not a subclass)
// System.out.println(account.holderName); // COMPILE ERROR
// package-private: NOT accessible
// System.out.println(account.branchCode); // COMPILE ERROR
// private: NOT accessible
// System.out.println(account.balance); // COMPILE ERROR
}
}
How does protected work with subclasses?
Protected fields are accessible to subclasses even in different packages:
package com.bank.savings; // DIFFERENT package
import com.bank.core.Account;
public class SavingsAccount extends Account {
public void showHolder() {
// protected: accessible because SavingsAccount is a subclass
System.out.println(this.holderName); // works!
// package-private: NOT accessible (different package)
// System.out.println(this.branchCode); // COMPILE ERROR
}
}
Important: the subclass can access protected members on this or on instances of itself. It cannot access them on an arbitrary Account reference from a different package.
What access modifiers are allowed on top-level classes?
Top-level classes (not nested inside another class) can only be:
- public – visible to all packages. The file must be named after the class.
- package-private (default) – visible only within its own package. No keyword needed.
// OK: public top-level class
public class Account { ... }
// OK: package-private top-level class
class InternalHelper { ... }
// NOT OK: private top-level class
// private class Secret { ... } // COMPILE ERROR
// NOT OK: protected top-level class
// protected class SemiPublic { ... } // COMPILE ERROR
Private and protected only make sense for members (fields, methods, inner classes) that are inside another class. A top-level class has no enclosing class to be private or protected relative to.
What access modifiers are allowed on nested/inner classes?
Nested classes can use all four modifiers:
public class Account {
public static class Builder { ... } // public nested
protected static class Validator { ... } // protected nested
static class Formatter { ... } // package-private nested
private static class Cache { ... } // private nested
}
What is the correct encapsulation pattern for banking entities?
public class Account {
// ALL fields private – never expose state directly
private final String accountId;
private BigDecimal balance;
private boolean active;
// Constructor validates inputs
public Account(String accountId, BigDecimal initialBalance) {
this.accountId = Objects.requireNonNull(accountId);
this.balance = initialBalance;
this.active = true;
}
// Public methods control access with validation
public void debit(BigDecimal amount) {
if (amount.signum() <= 0) throw new IllegalArgumentException("Positive amount required");
if (balance.compareTo(amount) < 0) throw new IllegalStateException("Insufficient funds");
balance = balance.subtract(amount);
}
// Read-only access
public BigDecimal getBalance() { return balance; }
public String getAccountId() { return accountId; }
public boolean isActive() { return active; }
// Internal helper – private, not part of the API
private void audit(String action, BigDecimal amount) {
// log for internal audit trail
}
}
Rule: fields are always private. Methods that external code calls are public. Methods used only within the class are private. Methods used within the package are default (package-private).
What are common mistakes?
- Making fields public.
public BigDecimal balancelets anyone set it to a negative number. Useprivate+ controlled methods. - Using protected when default is enough. Protected is for inheritance across packages. If you do not need that, use default.
- Confusing default with public. If no modifier is written, the member is package-private (NOT public).
void transfer()is only visible in the same package. - Accessing protected via a different class reference. A subclass in another package can access
this.protectedFieldbut notsomeOtherAccount.protectedField.
What should a senior engineer know?
- Modules (Java 9+) add another layer. Even public classes must be
exports-ed from their module to be visible to other modules. A public class in a non-exported package is invisible outside the module. - Interfaces: methods are implicitly public. You cannot make an interface method private (before Java 9) or package-private. Java 9+ allows private methods in interfaces for code reuse within default methods.
- Reflection can bypass access modifiers.
field.setAccessible(true)makes a private field accessible. But Java modules restrict this via--add-opens. - Records: components generate public getters. A record component
String accountIdgenerates a publicaccountId()method. The component itself is private final. - Encapsulation is not just about getters. A
setBalance(BigDecimal)method is almost as bad as a public field. Provide behavior methods (debit,credit) instead of setters.
Visual Overview
Access modifier visibility:
Same class Same package Subclass (other pkg) Other package
public Yes Yes Yes Yes
protected Yes Yes Yes No
default Yes Yes No No
private Yes No No No
Top-level class: only public or default
Inner class: all four modifiers allowed
Banking pattern:
Fields → private (always)
Getters → public
Behavior → public (debit, credit)
Helpers → private or default
Interfaces → public
Key Takeaway
Java has four access modifiers that control visibility: public (everywhere), protected (same package plus subclasses), default or package-private (same package only, no keyword written), and private (same class only).
In banking code, the pattern is consistent. All fields are private to prevent external code from corrupting state. Public methods like debit() and credit() validate inputs and enforce business rules. Internal helper methods are private. Classes shared within the package but not with external code use package-private access.
Top-level classes can only be public or package-private. Private and protected are only for members inside a class. And since Java 9, the module system adds another layer: even public classes must be explicitly exported from their module to be visible to other modules.
One sentence to remember: public means everywhere, protected means package plus subclasses, default means same package, private means same class – and fields should always be private.
---