Java Access Modifiers Explained — public, private, protected and default
Every piece of software you have ever used — your bank app, your favourite game, a weather app — is built on one golden rule: not everything should be accessible to everyone. When a banking app processes your PIN, it doesn't let any random part of the program read or change that number freely. That restriction is enforced in Java using access modifiers, and they are one of the very first tools a Java developer reaches for when designing anything serious.
Without access modifiers, every variable, method, and class in your program would be completely exposed to every other part of the codebase. That sounds harmless on a small project, but on a real-world application with hundreds of classes and multiple developers, it creates chaos. Someone accidentally modifies a value they shouldn't touch, a bug appears, and nobody knows where it came from. Access modifiers solve this by letting you draw clear boundaries — you decide exactly what is visible and what is locked away.
By the end of this article you'll know all four Java access modifiers, understand exactly when and why to use each one, be able to spot access-related compiler errors and fix them instantly, and feel confident answering access modifier questions in a Java interview. Let's build this up from scratch.
The Four Access Modifiers and What They Actually Mean
Java gives you four access levels. Think of them as four different lock types on a door. From most open to most locked, they are: public, protected, default (no keyword), and private.
'public' means absolutely anyone can access it — any class, in any package, anywhere in the project. It's the front door of a shop: open to the world.
'protected' means the class itself, any class in the same package, and any subclass (a class that inherits from this one) can access it. Think of a family recipe — you share it with family members and people in your household, but not strangers.
'default' (also called package-private) is what you get when you write NO modifier at all. Only classes inside the same package can access it. It's like an unwritten agreement between neighbours — people on the same street can use the shared path, but outsiders can't.
'private' is the strictest. Only the class that owns the member can access it. Nobody else — not even a subclass — can touch it directly. It's your diary with a lock.
These aren't just theory. They map directly to how Java enforces encapsulation, which is one of the four pillars of object-oriented programming. Getting these right is the difference between code that's easy to maintain and code that becomes a nightmare.
// This file demonstrates all four access modifiers in one place. // Run this as a single file (Java 11+) or create the classes in separate files. // A public class — visible to everyone, everywhere public class AccessModifierDemo { // public field — any code in the entire project can read/change this public String brandName = "TheCodeForge"; // protected field — accessible in this class, same package, and subclasses protected int articleCount = 42; // default (no keyword) field — only accessible within the same package String internalCategory = "Java Basics"; // no modifier = package-private // private field — ONLY this class can read or change this value private String editorPassword = "s3cr3tP@ss"; // public method — anyone can call this public void showBrandName() { // This method is the 'front door' — safe to expose publicly System.out.println("Brand: " + brandName); } // private method — internal helper, no outside class should call this private String encryptPassword(String rawPassword) { // Hiding the implementation detail — callers don't need to know HOW return "[ENCRYPTED]" + rawPassword.hashCode(); } // public method that uses the private method internally public void printEncryptedPassword() { // We expose a safe action publicly, but keep the logic private System.out.println("Encrypted: " + encryptPassword(editorPassword)); } public static void main(String[] args) { AccessModifierDemo demo = new AccessModifierDemo(); // Accessing public field directly — perfectly fine System.out.println("Public field: " + demo.brandName); // Accessing protected field from within the SAME class — fine System.out.println("Protected field: " + demo.articleCount); // Accessing default field from within the SAME package — fine System.out.println("Default field: " + demo.internalCategory); // Accessing private field from within the SAME class — fine System.out.println("Private field: " + demo.editorPassword); // Calling public method — fine from anywhere demo.showBrandName(); // Calling public method that internally uses a private method demo.printEncryptedPassword(); // NOTE: If you tried to call demo.encryptPassword() from OUTSIDE // this class, the compiler would throw an error. Try it and see! } }
Protected field: 42
Default field: Java Basics
Private field: s3cr3tP@ss
Brand: TheCodeForge
Encrypted: [ENCRYPTED]-1722786358
private and public in Practice — The Bank Account Example
The most important pair to master first is private and public — and the classic teaching example is a bank account, because it maps perfectly to the real world.
Imagine a BankAccount class. It has a balance. Should the balance field be public? Absolutely not. If balance is public, any other class in your program can write something like: account.balance = 1000000; — with no checks, no history, no validation. That's terrifying.
Instead, you make balance private. Now nobody outside the class can touch it directly. You then provide public methods — called getters and setters — that control all access. The getter lets someone READ the balance. The setter (or a deposit/withdraw method) lets someone CHANGE it, but only after you've run whatever checks you need. This pattern is called encapsulation, and it's the whole point of private.
Public is the opposite philosophy — use it for things you genuinely want to expose as part of your class's contract with the rest of the world. Method names, constructors, anything another class legitimately needs to call. The golden rule: default to private first. Only make something public if you have a clear reason to.
public class BankAccount { // private — no outside class can directly read or write the balance // This is the single most important use of 'private' you'll encounter private double balance; // private — internal account identifier, nobody outside needs this raw private String accountNumber; // public constructor — anyone who wants to create an account can do so public BankAccount(String accountNumber, double openingBalance) { this.accountNumber = accountNumber; // Validate even at construction time — private data, our rules if (openingBalance < 0) { throw new IllegalArgumentException("Opening balance cannot be negative."); } this.balance = openingBalance; } // public getter — allows READ access to balance, but not direct WRITE access public double getBalance() { return balance; // we return a copy of the value, not the field itself } // public method to deposit money — enforces our business rules public void deposit(double amount) { if (amount <= 0) { System.out.println("Deposit amount must be positive. Ignoring."); return; } balance += amount; // only THIS class modifies balance directly System.out.println("Deposited £" + amount + ". New balance: £" + balance); } // public method to withdraw money — enforces our business rules public void withdraw(double amount) { if (amount <= 0) { System.out.println("Withdrawal amount must be positive. Ignoring."); return; } if (amount > balance) { // private data lets us protect this logic completely System.out.println("Insufficient funds. Balance is only £" + balance); return; } balance -= amount; System.out.println("Withdrew £" + amount + ". New balance: £" + balance); } // private helper — only used internally, no outside class needs to know this exists private boolean isHighValueAccount() { return balance > 10000; } // public method that uses the private helper — safe, controlled exposure public void printAccountSummary() { System.out.println("Account: " + accountNumber); System.out.println("Balance: £" + balance); System.out.println("High-value account: " + isHighValueAccount()); } public static void main(String[] args) { BankAccount myAccount = new BankAccount("ACC-00192", 500.00); // Reading balance through the public getter — correct approach System.out.println("Initial balance: £" + myAccount.getBalance()); myAccount.deposit(250.00); myAccount.withdraw(100.00); myAccount.withdraw(800.00); // should fail — insufficient funds System.out.println(); myAccount.printAccountSummary(); // The line below would cause a COMPILE ERROR if you uncommented it: // myAccount.balance = 999999; // ERROR: balance has private access in BankAccount // myAccount.isHighValueAccount(); // ERROR: isHighValueAccount() has private access } }
Deposited £250.0. New balance: £750.0
Withdrew £100.0. New balance: £650.0
Insufficient funds. Balance is only £650.0
Account: ACC-00192
Balance: £650.0
High-value account: false
protected and Default — When Packages and Inheritance Come In
Once you understand private and public, the next step is understanding when you need something in between. That's where protected and default (package-private) come in — and they're the two most confused access modifiers for beginners.
Default (no keyword) is simpler: if two classes are in the same package — the same folder, essentially — they can see each other's default members. This is useful for utility classes or helper logic that belongs to a module but shouldn't be exposed as part of a public API. Think of it as 'internal' — visible within your team's workspace, invisible to the outside world.
Protected goes one step further: it adds inheritance to the mix. A protected member is visible in the same package AND in any subclass, even if that subclass is in a completely different package. This is specifically designed for the parent-child class relationship in object-oriented programming. You use protected when you're building a base class and you know subclasses will need to access or override something, but you still don't want the whole world touching it.
A good real-world analogy: a protected family recipe. Your children (subclasses) can have it and adapt it. Strangers (unrelated classes outside the package) cannot.
// === FILE 1: Vehicle.java (in package: com.codeforge.vehicles) === // Imagine this is in: com/codeforge/vehicles/Vehicle.java package com.codeforge.vehicles; public class Vehicle { // public — any class anywhere can read the brand public String brand; // protected — this class AND any subclass (even in other packages) can access this // It's the parent sharing something specifically with its children protected int engineCapacityCC; // default (no modifier) — only classes in com.codeforge.vehicles can see this String factoryLocation; // package-private // private — only Vehicle itself can touch this private String internalSerialCode; public Vehicle(String brand, int engineCapacityCC, String factoryLocation, String internalSerialCode) { this.brand = brand; this.engineCapacityCC = engineCapacityCC; this.factoryLocation = factoryLocation; this.internalSerialCode = internalSerialCode; } // protected method — subclasses can call and even override this protected String getEngineDescription() { return brand + " engine: " + engineCapacityCC + "cc"; } // public method — the 'front door' for external callers public void displayInfo() { System.out.println("Brand: " + brand); System.out.println("Engine: " + engineCapacityCC + "cc"); System.out.println("Factory: " + factoryLocation); // Internal serial code stays private — even displayInfo() is careful about it System.out.println("Serial: [RESTRICTED]"); } } // === FILE 2: ElectricCar.java (in package: com.codeforge.electric) === // A DIFFERENT package — notice it can still access the protected member // Imagine this is in: com/codeforge/electric/ElectricCar.java package com.codeforge.electric; import com.codeforge.vehicles.Vehicle; // importing the parent class public class ElectricCar extends Vehicle { // 'extends' means ElectricCar is a subclass of Vehicle private int batteryRangeKm; public ElectricCar(String brand, int batteryRangeKm) { // Calling the parent constructor — electric cars have near-zero CC engine super(brand, 0, "Tesla Gigafactory", "EV-" + brand + "-001"); this.batteryRangeKm = batteryRangeKm; } @Override protected String getEngineDescription() { // Subclass CAN access engineCapacityCC because it's protected // Even though ElectricCar is in a DIFFERENT package, inheritance allows this return brand + " electric motor (no CC), range: " + batteryRangeKm + "km"; } public void showElectricInfo() { // We can call the protected method because we're a subclass System.out.println(getEngineDescription()); // We can access protected field engineCapacityCC directly System.out.println("Engine CC (should be 0 for EV): " + engineCapacityCC); // We CANNOT access factoryLocation here — it's default/package-private // and ElectricCar is in a DIFFERENT package. Uncommenting this would fail: // System.out.println(factoryLocation); // COMPILE ERROR } } // === FILE 3: Main.java (in package: com.codeforge.electric) === package com.codeforge.electric; public class Main { public static void main(String[] args) { ElectricCar tesla = new ElectricCar("Tesla Model 3", 560); // Public method — accessible from anywhere tesla.displayInfo(); System.out.println(); // Calling the subclass-specific method tesla.showElectricInfo(); // Accessing public field directly — fine System.out.println("\nBrand directly: " + tesla.brand); // From OUTSIDE the class hierarchy, protected members are NOT accessible: // System.out.println(tesla.engineCapacityCC); // COMPILE ERROR from here } }
Engine: 0cc
Factory: Tesla Gigafactory
Serial: [RESTRICTED]
Tesla Model 3 electric motor (no CC), range: 560km
Engine CC (should be 0 for EV): 0
Brand directly: Tesla Model 3
Access Modifiers on Classes — Not Just Fields and Methods
Everything we've covered so far applies to fields and methods inside a class. But access modifiers also control the class itself — and this trips up a lot of beginners.
A top-level class (a class not nested inside another class) can only be public or default (no modifier). You cannot make a top-level class private or protected — the Java compiler will throw an error immediately. This makes sense: a private class that nothing can see would be completely useless.
A public class is visible to everyone. A default class is visible only within its own package. This is useful when you want to create helper classes that are only relevant internally — you don't want other packages to depend on them.
However, inner classes (classes defined inside another class) can use all four access modifiers. A private inner class is a popular pattern for implementation details that are tightly coupled to their outer class.
One important Java rule: if a file contains a public class, the filename MUST match that class name exactly (including case). If your class is 'public class UserProfile', your file must be 'UserProfile.java'. Get this wrong and the compiler will refuse to compile. This is why you'll rarely see more than one public class per file.
// This file demonstrates access modifiers applied at the CLASS level // A single .java file can have only ONE public class // The filename must match: ClassAccessModifierDemo.java public class ClassAccessModifierDemo { // private inner class — completely hidden from outside // Only ClassAccessModifierDemo can create or use a Node // This is a common pattern in data structures (LinkedList nodes, tree nodes, etc.) private class Node { int value; Node nextNode; // points to the next node in a chain Node(int value) { this.value = value; this.nextNode = null; } } // public inner class — anyone can use this if they have a ClassAccessModifierDemo instance public class Statistics { private int totalNodes; public Statistics(int totalNodes) { this.totalNodes = totalNodes; } public void printStats() { System.out.println("Total nodes in chain: " + totalNodes); } } // The outer class uses its private inner class freely private Node firstNode; private int nodeCount; // public method — builds an internal chain using private Node objects public void addValue(int value) { Node newNode = new Node(value); // Node is private, but we're inside the owner class if (firstNode == null) { firstNode = newNode; } else { // Walk to the end of the chain Node currentNode = firstNode; while (currentNode.nextNode != null) { currentNode = currentNode.nextNode; } currentNode.nextNode = newNode; // attach the new node at the end } nodeCount++; } // public method — lets outsiders read values without knowing Node exists public void printAllValues() { System.out.print("Chain: "); Node currentNode = firstNode; while (currentNode != null) { System.out.print(currentNode.value); if (currentNode.nextNode != null) System.out.print(" -> "); currentNode = currentNode.nextNode; } System.out.println(); } public Statistics getStatistics() { return new Statistics(nodeCount); // returns a public inner class instance } public static void main(String[] args) { ClassAccessModifierDemo chain = new ClassAccessModifierDemo(); chain.addValue(10); chain.addValue(25); chain.addValue(37); chain.printAllValues(); Statistics stats = chain.getStatistics(); stats.printStats(); // The line below would cause a COMPILE ERROR: // ClassAccessModifierDemo.Node n = new ClassAccessModifierDemo.Node(5); // ERROR: Node has private access in ClassAccessModifierDemo } }
Total nodes in chain: 3
| Access Level | Same Class | Same Package | Subclass (diff. package) | Any Class (anywhere) |
|---|---|---|---|---|
| public | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| protected | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No |
| default (no keyword) | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
| private | ✅ Yes | ❌ No | ❌ No | ❌ No |
🎯 Key Takeaways
- There are exactly four access levels in Java — public, protected, default (no keyword), and private — each one progressively more restrictive than the last.
- Default access (no keyword) is NOT 'open to everything' — it means package-private. Only classes in the same package can see it. This surprises developers who assume 'no modifier = no restriction'.
- protected is specifically designed for the inheritance relationship: subclasses in any package can access protected members, but unrelated classes outside the package cannot.
- The golden rule of encapsulation: default to private for all fields. Only make something public or protected when you have a clear, deliberate reason. Opening things up is easy — locking them down later when other code depends on them is painful.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Making all fields public for convenience — Symptom: Other classes can change your object's data without any validation, causing hard-to-trace bugs (e.g., a negative age or a null username). Fix: Always declare fields as private. Provide public getters for read access and carefully controlled setters or action methods for write access. The extra typing is worth every second.
- ✕Mistake 2: Confusing protected with private when it comes to subclasses — Symptom: A developer makes a field private, then writes a subclass that needs to access it, and gets 'has private access' compiler error. They're confused because the subclass 'is a' version of the parent. Fix: Use protected for fields or methods you specifically intend subclasses to access or override. Private means even child classes are locked out — by design.
- ✕Mistake 3: Forgetting that default access is NOT 'no restriction' — Symptom: A class in a different package tries to use a default-access method and gets a compile error that says 'is not public in [class]; cannot be accessed from outside package', which surprises beginners who assumed no modifier means no restriction. Fix: Understand that omitting a modifier is a deliberate choice meaning 'package-private'. If you want something universally accessible, you must explicitly write 'public'.
Interview Questions on This Topic
- QWhat is the difference between protected and default (package-private) access in Java? Can a subclass in a different package access a protected member? What about a default member?
- QWhy would you ever use private access for a field when a getter method that returns the same value is also public — isn't that the same thing?
- QIf a class has no access modifier on its constructor, can you instantiate it from a different package? What error would you see, and how would you fix it?
Frequently Asked Questions
What is the default access modifier in Java?
The default access level in Java is called package-private. You get it by writing NO modifier at all before the field, method, or class. It means only classes within the same package can access that member. It's NOT a keyword — writing 'default int count = 0;' is actually a syntax error.
Can a subclass access the private members of its parent class in Java?
No. Private members are strictly limited to the class they're declared in — not even subclasses can access them directly. If a parent class has a private field that subclasses need to work with, the parent should provide a protected getter method or use protected access on the field itself.
What is the difference between private and protected in Java?
Private restricts access to the single class where the member is declared — no other class can touch it, including subclasses. Protected is less restrictive: it allows access from the same class, any class in the same package, AND any subclass regardless of package. Use private when you want to hide implementation details completely, and protected when you're designing a class that's meant to be extended.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.