Access Modifiers (public, private, protected)
Reviewed & published by Brayan K
Good objects hide their inner workings and expose only what's safe to touch. Access modifiers are the keywords that draw that boundary — they decide who can read and change each field, method, and class. Master them and you master encapsulation.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Before You Start
You'll want to be comfortable with classes and objects (fields, methods, constructors) and have seen inheritance, since protected only makes sense alongside subclasses.
What You'll Learn
- The four access levels: public, protected, default, private
- What encapsulation is and why hiding state matters
- Why the default (no keyword) is package-private, not public
- How protected differs from package-private
- Writing getters and setters with validation
- Where each modifier is and isn't allowed
The Big Idea — Doors in a House
💡 Analogy: Think of a class as a house. public members are the front door — anyone may walk through. protected is a door for family (subclasses) and neighbours in the same street (package). Package-private is a gate only your street's neighbours can open. private is your bedroom safe — only you, inside the house, can open it. Encapsulation is choosing the right door for each room so visitors can't wander into the wiring.
The golden rule: make everything as private as you can, and only open it up when you have a concrete reason. A small public surface is easier to use, test, and change without breaking callers.
1️⃣ The Four Access Levels
Java has exactly four levels. Note that the third one — package-private — has no keyword: you get it by writing nothing at all.
| Modifier | Same class | Same package | Subclass (other pkg) | Anywhere |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ✅ | ❌ |
| (default) | ✅ | ✅ | ❌ | ❌ |
| private | ✅ | ❌ | ❌ | ❌ |
public class Main {
static class Demo {
public String pub = "anyone can read me";
protected String prot = "package + subclasses";
String pkg = "package-private (no keyword)";
private String priv = "only inside Demo";
public void show() {
// Inside the SAME class, every level is reachable:
System.out.println("public : " + pub);
System.out.println("protected: " + prot);
System.out.println("default : " + pkg);
System.out.println("private : " + priv);
}
}
public static void main(String[] args) {
Demo d = new Demo();
d.show();
// From another class in the SAME package we can reach all but private:
System.out.println("Read from outside -> public: " + d.pub);
System.out.println("Read from outside -> default: " + d.pkg);
// System.out.println(d.priv); // <- will NOT compile
}
}2️⃣ Encapsulation — Why Hide State?
Encapsulation means keeping an object's data private and exposing only safe, controlled methods to work with it. If a bank account's balance were public, any code could set it to -9999 and break the rules. Making it private forces every change to go through deposit and withdraw, which can enforce "no overdrafts" and "no negative deposits".
The example below is the classic case: the field is locked away, and the public methods are the only door in.
public class Main {
// A class that protects its internal state with 'private'
static class BankAccount {
private double balance; // hidden — only this class touches it
public BankAccount(double opening) {
this.balance = opening;
}
// public methods are the controlled "door" into the object
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false; // reject overdraft
}
public double getBalance() { // read-only access (a "getter")
return balance;
}
}
public static void main(String[] args) {
BankAccount acc = new BankAccount(100.0);
acc.deposit(50.0);
System.out.println("After deposit: " + acc.getBalance());
boolean ok = acc.withdraw(200.0); // too much — rejected
System.out.println("Withdraw 200 succeeded? " + ok);
System.out.println("Balance still: " + acc.getBalance());
// acc.balance = -999; // <- would NOT compile: balance is private
}
}3️⃣ Getters & Setters with Validation
A getter returns a private field; a setter changes it. The reason they exist — instead of a public field — is that a setter can validate before storing. You can also expose a getter with no setter to make a field read-only, or compute a value on the fly that has no backing field at all.
public double getCelsius() { return celsius; } // read
public void setCelsius(double c) { // write, validated
if (c < -273.15) throw new IllegalArgumentException("too cold");
this.celsius = c;
}Naming convention: getters are getX() (or isX() for booleans) and setters are setX(value). Many frameworks (and the records feature) rely on this convention.
public class Main {
static class Temperature {
private double celsius;
public double getCelsius() { return celsius; }
// A setter can VALIDATE before storing — the whole point of private
public void setCelsius(double c) {
if (c < -273.15) {
throw new IllegalArgumentException("Below absolute zero!");
}
this.celsius = c;
}
// A derived, read-only property — no field needed
public double getFahrenheit() {
return celsius * 9 / 5 + 32;
}
}
public static void main(String[] args) {
Temperature t = new Temperature();
t.setCelsius(25);
System.out.println(t.getCelsius() + "C = " + t.getFahrenheit() + "F");
try {
t.setCelsius(-300); // invalid — guarded by the setter
} catch (IllegalArgumentException e) {
System.out.println("Rejected: " + e.getMessage());
}
System.out.println("Still safe at " + t.getCelsius() + "C");
}
}4️⃣ protected — For Subclasses
protected opens a member to subclasses (and to the same package), while still hiding it from unrelated outside code. Use it for the parts of a parent class that a child legitimately needs to read or override — but keep truly internal details private, hidden even from subclasses.
public class Main {
static class Animal {
protected String name; // subclasses may use this directly
private int secretId = 7; // hidden even from subclasses
Animal(String name) { this.name = name; }
protected String describe() { // subclasses may override/extend
return name + " (id hidden)";
}
}
static class Dog extends Animal {
Dog(String name) { super(name); }
String bark() {
// 'name' is protected -> accessible in the subclass:
return name + " says Woof!";
// secretId is private -> NOT accessible here
}
}
public static void main(String[] args) {
Dog d = new Dog("Rex");
System.out.println(d.bark());
System.out.println(d.describe());
}
}🧩 Reorder Challenge
These lines form a properly encapsulated Score class with a private field and a validating setter. They've been shuffled.
A) public int getValue() { return value; }
B) class Score {
C) if (v >= 0) value = v;
D) private int value;
E) public void setValue(int v) {
F) }
G) }Correct order: B, D, A, E, C, F, G
class Score {
private int value;
public int getValue() { return value; }
public void setValue(int v) {
if (v >= 0) value = v;
}
}Open the class, declare the private field, then the getter, then the setter whose body validates before storing, and finally close both the method and the class.
🧠 Quick Recall
Predict the result before opening each answer.
class Box { private int n = 5; }
class Main {
public static void main(String[] args) {
Box b = new Box();
System.out.println(b.n);
}
}Answer: It does not compile. n is private, so it is invisible to Main even though both are in the same file: error: n has private access in Box. You'd need a public getter to read it.
class Account {
private double balance = 100;
public boolean withdraw(double amt) {
if (amt <= balance) { balance -= amt; return true; }
return false;
}
public double balance() { return balance; }
}
Account a = new Account();
System.out.println(a.withdraw(150) + " " + a.balance());Answer: false 100.0. The withdrawal of 150 exceeds the balance of 100, so withdraw returns false and leaves balance untouched — exactly the guarantee private + a guarded method gives you.
class Animal { protected String name = "?"; private int id = 1; }
class Cat extends Animal {
String describe() {
return name + " " + id; // <-- this line
}
}Answer: The return line fails because id is private — even a subclass can't see it. name is fine because it's protected. Remove id (or make it protected) and it compiles.
🎯 Your Turn #1 — Encapsulate a counter
Make the field private and provide controlled access. The count must only ever go up via increment().
public class Main {
static class Counter {
// YOUR TURN: make 'count' private, then add a public increment()
// method and a public getCount() method. The field must not be
// changeable from outside except through increment().
int count = 0;
// add increment() and getCount() here
}
public static void main(String[] args) {
Counter c = new Counter();
c.increment();
c.increment();
c.increment();
System.out.println("Count: " + c.getCount()); // Expected: Count: 3
}
}🎯 Your Turn #2 — Validating setter
Write a setter that rejects an email without an @, keeping the previous value.
public class Main {
static class User {
private String email;
// YOUR TURN: write setEmail that only stores the value if it
// contains an '@'. Otherwise leave the old value untouched.
// Then write getEmail.
// Expected output:
// Email: ok@site.com
// Email: ok@site.com (the bad value "nope" was rejected)
}
public static void main(String[] args) {
User u = new User();
u.setEmail("ok@site.com");
System.out.println("Email: " + u.getEmail());
u.setEmail("nope");
System.out.println("Email: " + u.getEmail());
}
}🧩 Mini-Challenge — Hide a password
Build a Password class that lets callers set and check a password but never read it back.
public class Main {
// GOAL: a Password class that NEVER exposes the raw password.
//
// Expected output:
// Set? true
// Matches 'hunter2'? true
// Matches 'wrong'? false
// Stored length: 7
static class Password {
// 1. a private String field for the password
// 2. public boolean set(String p): store only if p.length() >= 4, return success
// 3. public boolean matches(String guess): true if guess equals the stored value
// 4. public int length(): the stored length (do NOT expose the value itself)
}
public static void main(String[] args) {
Password p = new Password();
System.out.println("Set? " + p.set("hunter2"));
System.out.println("Matches 'hunter2'? " + p.matches("hunter2"));
System.out.println("Matches 'wrong'? " + p.matches("wrong"));
System.out.println("Stored length: " + p.length());
}
}Common Errors
- ❌ Making fields public "for convenience". Any code can then corrupt them. Fix: make fields private and add methods that enforce your rules.
- ❌ Assuming default means public. No keyword means package-private, the second-most-restrictive level. Fix: add public explicitly when you mean public.
- ❌ private access from another class. error: x has private access in Y. Fix: add a getter/setter, or rethink whether that class should reach in at all.
- ❌ Declaring a top-level class private or protected. error: modifier private not allowed here. Fix: a top-level class can only be public or package-private.
- ❌ protected on a member you only need within one package. It needlessly exposes the member to outside subclasses. Fix: use package-private (no keyword) instead.
- ❌ A setter with no validation. If setX just assigns, it gives you nothing over a public field. Fix: only add a setter when it validates, logs, or notifies — otherwise consider a read-only field.
Pro Tips
- 💡 Start private, open up reluctantly. It's far easier to widen access later than to take it away once callers depend on it.
- 💡 Prefer immutable objects. A field that is private final and set once in the constructor needs no setter at all — and can't be corrupted.
- 💡 Constructors can be private too, which is how singletons and factory methods control object creation.
- 💡 Records and Lombok generate getters for you, but the principle is the same: a small, deliberate public surface.
📋 Quick Reference
| Modifier | Visible to | Use for |
|---|---|---|
| public | Everyone | Your intended API |
| protected | Package + subclasses | Hooks for subclasses |
| (none) | Same package only | Internal package helpers |
| private | Same class only | Fields, internal state |
| private final | Same class, set once | Immutable fields |
🎉 Lesson Complete!
You now know all four access levels, why the keyword-less default is package-private, how protected reaches subclasses, and how private fields plus validating getters/setters give you real encapsulation. The habit to keep: make everything as private as possible.
Practice quiz
Which access level applies when you write a field with NO access keyword at all?
- public
- protected
- package-private (default)
- private
Answer: package-private (default). No keyword means package-private: visible only within the same package, narrower than protected.
From most open to most restrictive, which ordering is correct?
- private, default, protected, public
- public, protected, default (package-private), private
- public, default, protected, private
- protected, public, private, default
Answer: public, protected, default (package-private), private. public ⊃ protected ⊃ package-private ⊃ private. Each level adds reach to the one below it.
What is the key difference between protected and package-private?
- They are identical
- protected also reaches subclasses in other packages; package-private does not
- package-private is broader than protected
- protected hides a member from the same package
Answer: protected also reaches subclasses in other packages; package-private does not. protected adds cross-package inheritance access on top of same-package access; package-private stays within one package.
A top-level class (not nested) may be declared as:
- public or package-private only
- any of the four levels
- private or protected only
- protected only
Answer: public or package-private only. private and protected are not allowed on a top-level class — only public or package-private (no keyword).
Why make a field private and expose a setter instead of a public field?
- It runs faster
- So the setter can validate input before storing it
- Private fields use less memory
- It is required by the compiler
Answer: So the setter can validate input before storing it. A setter can validate, log, or reject bad values; a public field can be set to anything, breaking invariants.
Inside a subclass, a parent's private field is:
- Accessible directly
- Accessible only with a cast
- NOT accessible — private hides it even from subclasses
- Accessible only in the same file
Answer: NOT accessible — private hides it even from subclasses. private means same class only. Subclasses cannot see a private member; use protected for members a child needs.
Two classes in the SAME file but different (top-level) classes. Class Main reads a private field of class Box. What happens?
- It compiles and prints the value
- It compiles with a warning
- Compile error: the field has private access in Box
- It prints null
Answer: Compile error: the field has private access in Box. private is invisible outside its own class even within one file. You need a public getter to read it.
A setter that just assigns the value with no validation gives you, over a public field:
- Much better performance
- Essentially nothing extra
- Automatic thread safety
- Compile-time constant checking
Answer: Essentially nothing extra. A no-op setter adds ceremony without benefit. Only add a setter when it validates, logs, or notifies.
Which combination best describes a field that can be read but never changed after construction?
- public field
- private final field set once in the constructor
- protected field with a setter
- package-private field
Answer: private final field set once in the constructor. private final, assigned once in the constructor, is immutable — no setter needed, and it cannot be corrupted.
The golden rule of access modifiers is:
- Make everything public for convenience
- Make everything as private as you can, opening up only with a concrete reason
- Always use protected
- Prefer package-private for all fields
Answer: Make everything as private as you can, opening up only with a concrete reason. A small public surface is easier to use, test, and change. Widen access only when you truly need to.
Continue this course
- Previous: Inheritance
- Next: Packages & Imports — Organise classes into packages and import them
- Quick reference: Java cheat sheet
Frequently asked questions
What are the four access levels in Java?
From most open to most restrictive: public (any code anywhere), protected (the same package plus subclasses in other packages), package-private (the same package only — this is the default when you write no keyword), and private (only inside the same class). Choosing the right one is how you control your code's public surface.
What is the default access level if I don't write a keyword?
Package-private. A member with no modifier is visible to any class in the same package but invisible everywhere else — not even to subclasses in a different package. There is no 'default' keyword; the absence of a keyword IS the default. It's more restrictive than protected, which surprises many beginners.
Why make fields private and add getters and setters?
This is encapsulation. A private field can only change through methods you control, so you can validate input (reject a negative balance), keep derived values consistent, log changes, or make a field read-only by providing a getter but no setter. If the field were public, any code could set it to anything and break your object's invariants. Private fields also let you change the internal representation later without breaking callers.
What is the difference between protected and package-private?
Package-private (no keyword) is visible only within the same package. protected is visible within the same package AND to subclasses even in other packages. So protected is strictly broader: it adds cross-package inheritance access on top of package access. Use protected for members a subclass legitimately needs; use package-private to share within one package without exposing anything to the outside world.
Can a top-level class be private or protected?
No. A top-level class (one not nested inside another) can only be public or package-private. private and protected are not allowed there because they have no meaning at file scope. Those two modifiers are only valid on members (fields, methods, constructors) and on nested classes.