Access Modifiers in Java
Access modifiers control who can see and use your classes, methods, and variables. Getting this wrong leads to broken encapsulation, security issues, and tightly coupled code.
The Four Access Levels
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph PUBLIC["🌍 public — Visible Everywhere"]
subgraph PROTECTED["🏘️ protected — Same Package + Subclasses"]
subgraph DEFAULT["📦 default — Same Package Only"]
subgraph PRIVATE["🔒 private — Same Class Only"]
P(("Fields & Methods"))
end
end
end
end
style PUBLIC fill:#ECFDF5,stroke:#6EE7B7,stroke-width:3px,color:#065F46
style PROTECTED fill:#EFF6FF,stroke:#DBEAFE,stroke-width:2px,color:#1E40AF
style DEFAULT fill:#FFFBEB,stroke:#FCD34D,stroke-width:2px,color:#92400E
style PRIVATE fill:#FEF2F2,stroke:#FCA5A5,stroke-width:2px,color:#991B1B
style P fill:#EFF6FF,stroke:#93C5FD,color:#1E40AF %%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph scope["🎯 Quick Reference"]
direction LR
PUB{{"🟢 public"}} --> |"Any class, any package"| ALL(["✅ Everyone"])
PROT{{"🔵 protected"}} --> |"Same package OR subclass"| SUB(["✅ Package + Children"])
DEF{{"🟠 default"}} --> |"Same package only"| PKG(["✅ Package Only"])
PRIV{{"🔴 private"}} --> |"Same class only"| CLS(["✅ Class Only"])
end
style PUB fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
style PROT fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style DEF fill:#FEF3C7,stroke:#FCD34D,color:#92400E
style PRIV fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B Visibility Table
| Modifier | Same class | Same package | Subclass (different package) | Any class |
|---|---|---|---|---|
private | Yes | No | No | No |
| default (no keyword) | Yes | Yes | No | No |
protected | Yes | Yes | Yes | No |
public | Yes | Yes | Yes | Yes |
Each Modifier in Detail
private — Class-level encapsulation
Only accessible within the same class. This is the default you should start with for all fields.
public class BankAccount {
private double balance; // no one outside can touch this directly
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
this.balance += amount;
}
public double getBalance() {
return balance; // controlled read access
}
}
Why it matters: If balance were public, any class could set it to -1000 and bypass validation.
default (Package-private) — No keyword
When you write no modifier at all, it's package-private. Only classes in the same package can access it.
class InternalHelper { // no 'public' — only visible in this package
void processData() {
// utility method for classes in this package only
}
}
When to use: Internal implementation classes that shouldn't be part of your public API.
protected — Package + subclasses
Accessible within the same package AND by subclasses in any package.
public class Animal {
protected String name; // subclasses can access
protected void makeSound() {
System.out.println("Some sound");
}
}
// Different package
public class Dog extends Animal {
public void bark() {
makeSound(); // OK — Dog is a subclass
System.out.println(name + " barks!"); // OK — protected field
}
}
public — Visible everywhere
public class UserService {
public User findById(Long id) { // API — anyone can call this
return userRepository.findById(id);
}
}
Access Modifiers on Classes
| Where | Allowed modifiers |
|---|---|
| Top-level class | public or default only |
| Inner class | All four (private, default, protected, public) |
| Local class (inside method) | No modifier allowed |
public class Outer {
private class Secret { } // only Outer can see this
protected class Helper { } // Outer + subclasses
public class PublicInner { } // everyone
class PackageInner { } // same package only
}
The Golden Rule: Start Private, Open Up as Needed
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
START(("🏁 Start Here")) --> PRIV{{"🔒 private"}}
PRIV -->|"Need package access?"| DEF{{"📦 default"}}
DEF -->|"Need subclass access?"| PROT{{"🛡️ protected"}}
PROT -->|"Need global access?"| PUB(["🌍 public"])
style START fill:#EFF6FF,stroke:#93C5FD,color:#1E40AF
style PRIV fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
style DEF fill:#FEF3C7,stroke:#FCD34D,color:#92400E
style PROT fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style PUB fill:#D1FAE5,stroke:#6EE7B7,color:#065F46 This is called the principle of least privilege. Always start with private and only increase visibility when you have a concrete reason.
Common Interview Traps
1. Can you access a protected member from a different package without extending the class?
No. This is the most common mistake. protected does NOT mean "accessible everywhere." A class in a different package must extend the class to access protected members. Simply importing the class is not enough.
2. What is the default access modifier for interface methods?
public. All methods in an interface are implicitly public. You cannot make them private (before Java 9) or protected. Since Java 9, private methods are allowed in interfaces for internal helper logic.
3. Can a top-level class be private?
No. A top-level class can only be public or default (package-private). If it were private, nothing could see it — it would be useless. Only inner classes can be private.
4. Why should fields always be private? What if I need external access?
Direct field access breaks encapsulation. If you make a field public, you can never add validation, logging, or lazy loading later without breaking all callers. Use private fields with getter/setter methods so you can control access. This is why frameworks like Spring and Hibernate require getters/setters.
5. What happens if a subclass tries to reduce the visibility of an overridden method?
Compile error. You can increase visibility (e.g., protected → public) but never decrease it. This is because of the Liskov Substitution Principle — code using the parent type expects the method to be accessible.