Encapsulation in Java
Private fields, controlled access through methods, validating invariants.
acct.balance = -500;. All that careful checking — bypassed in one line.A capsule with a control panel
Encapsulation: hide an object's fields and expose only methods that keep its data valid. Make fields private; let methods check every change, so rules (*invariants*) like "balance is never negative" always hold.
Guarding the balance
class Account {
public int balance;
}
// anyone: acct.balance = -500;Any code can skip your rules.
class Account {
private int balance;
void deposit(int amt) {
if (amt <= 0)
throw new IllegalArgumentException();
balance += amt;
}
int getBalance() { return balance; }
}Every change passes through a check.
The guard at work
What happens when this runs?
class Thermostat {
private int temp = 20;
void set(int t) {
if (t < 5 || t > 30)
throw new IllegalArgumentException();
temp = t;
}
}
void main() { new Thermostat().set(99); }Nothing, temp stays 20temp becomes 99Throws IllegalArgumentException
Show the answer
It throws IllegalArgumentException. The setter rejects the invalid value immediately, so the object can never hold a nonsense temperature.
Freedom to change the inside
Callers only see methods, never fields. So you can switch private double price to private long priceCents and keep getPrice() returning the same value — no caller breaks. Encapsulation hides how the data is stored.
The leaky getter
final only stops the field from pointing to a different list — the list itself is still mutable. Returning it hands callers a remote control to your data. Return List.copyOf(members) or Collections.unmodifiableList(members) instead.
class Team {
private final List<String> members =
new ArrayList<>();
List<String> getMembers() {
return members; // leak!
}
}Why not public and careful?
Why not skip all this and just ask everyone to be careful with public fields?
Think about it, then reveal the answer
Because "careful" doesn't scale. With a public field, any line anywhere can break the rule, you'd have to search the whole codebase to find who did, and you can never change the field's type without breaking every caller. Private fields make the class the single gatekeeper.
In real projects
Java's own String is the proof: in Java 9 its private storage switched from char[] to byte[] (Compact Strings) and not one program broke, because nobody could touch the field. That's the payoff of encapsulation at scale.
Key takeaways
- Fields private, behavior through methods
- Methods validate input and protect invariants
- Internals can change without breaking callers
- Don't leak references to mutable internal objects
💡 A bank teller: you can't reach into the vault, you ask the teller, who checks the rules first.
java.util.Date is a famous encapsulation headache: it's mutable, so a getter returning your Date field lets callers change it. That's one reason the java.time API (Java 8) made every type immutable.
Practice questions
What happens when this runs?
class Person {
private int age;
void setAge(int a) {
if (a < 0)
throw new IllegalArgumentException();
age = a;
}
}
void main() { new Person().setAge(-1); }- Nothing, age stays 0
- Throws IllegalArgumentException
- Compile error
- age becomes -1
Check your answer
Throws IllegalArgumentException. The setter guards the invariant: a negative age is rejected immediately, so the object can never hold an invalid value.
You change Product's private double price into long priceCents, and keep getPrice() returning the same value. Why does no caller break?
- Callers only used getPrice(), never the field itself
- The compiler converts double to long everywhere
- private fields are copied into callers at compile time
- Java ignores field types at runtime
Check your answer
Callers only used getPrice(), never the field itself. Because the field was hidden, the method is the only contract. You can change the internal representation freely as long as the method still behaves the same.