What are Access Modifiers?
In Java, Access Modifiers are keywords that set the level of access for classes, methods, and variables. They ensure data security and proper encapsulation in Object-Oriented Programming (OOP).
Java provides four types of access modifiers — each controlling how visible a member is across different parts of your codebase.
Types of Access Modifiers
Java has four access modifiers. Each has a different visibility scope — from the most restrictive to the most open:
Accessible from anywhere in the program — any class, any package, any module.
🌍 Universal AccessAccessible only within the same class. Most restrictive modifier — complete data hiding.
🔒 Same Class OnlyAccessible within the same package and by subclasses (even across packages via inheritance).
🛡 Package + SubclassesNo keyword needed. Accessible only within the same package. Also called package-private.
📦 Same Package OnlyAccess Scope at a Glance
This table shows where each modifier allows access — use it as a quick reference during development.
| Modifier | Same Class | Same Package | Subclass (diff pkg) | Anywhere |
|---|---|---|---|---|
| public | ✔ | ✔ | ✔ | ✔ |
| protected | ✔ | ✔ | ✔ | ✘ |
| default | ✔ | ✔ | ✘ | ✘ |
| private | ✔ | ✘ | ✘ | ✘ |
Advantages of Access Modifiers
- Encapsulation & Data Hiding — Access modifiers let you hide internal implementation details and expose only what is necessary, protecting the integrity of your objects.
- Security & Maintainability — Restricted access reduces the chances of unintended interference, making code easier to debug, test, and maintain over time.
- Controlled API Design — You can precisely define the public API of a class while keeping implementation details private — a core principle of good software architecture.
Limitations
- Overuse of private — Making everything private can reduce code reusability and make unit testing harder, as internal state becomes difficult to verify.
- Design Complexity — Improper use of modifiers — such as making implementation details public — can create tight coupling and fragile designs that are hard to refactor.
- Default Access Pitfalls — Relying on default (package-private) access can create unintended package dependencies and make code less portable across module boundaries.
Example Code
Here is a practical Java class demonstrating all four access modifiers together:
// Access Modifiers Demo class AccessDemo { // public — accessible from anywhere public int publicVar = 10; // private — accessible only within this class private int privateVar = 20; // protected — accessible in same package & subclasses protected int protectedVar = 30; // default (no keyword) — same package only int defaultVar = 40; // private method — internal use only private void showPrivate() { System.out.println("Private Method: " + privateVar); } // public getter — controlled access to private data public int getPrivateVar() { return privateVar; } public static void main(String[] args) { AccessDemo obj = new AccessDemo(); System.out.println("Public: " + obj.publicVar); System.out.println("Protected: " + obj.protectedVar); System.out.println("Default: " + obj.defaultVar); System.out.println("Private via getter: " + obj.getPrivateVar()); } }
Best Practices for Access Modifiers
Use private for all fields and internal helper methods — expose them through getters/setters only when required.
Expose only necessary APIs as public. Once exposed, it's a contract that is hard to change later.
Use protected when designing classes intended for inheritance — it signals a member is part of the inheritance API.
Avoid default (package-private) access unless you intentionally want package-level collaboration between tightly related classes.