Encapsulation in Java
Encapsulation hides internal state behind a deliberate public surface, so a class can guarantee its own rules stay true.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Definition
Encapsulation means keeping the data of a class private and allowing access only through methods the class chooses to expose. The class controls its own state, and no caller can put it into an invalid condition.
Why it exists
- Invariants hold. A rule such as "the balance is never negative" can be enforced in one place.
- The implementation can change. Internal fields can be renamed, replaced or computed without breaking callers.
- The surface is small. Readers only need to understand the public methods.
- Bugs are localised. If the state is wrong, only this class could have made it wrong.
Without encapsulation
public class Account {
public double balance; // anyone can write anything
}
Account a = new Account();
a.balance = -50000; // the rule is broken, and nothing stopped itWith encapsulation
public class Account {
private double balance;
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be positive");
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Withdrawal must be positive");
}
if (amount > balance) {
throw new IllegalStateException("Insufficient balance");
}
balance -= amount;
}
public double getBalance() {
return balance;
}
}There is no setter for balance, and that is the point. The balance changes only through operations that make sense in the domain.
Encapsulation is not "write a getter and a setter for every field". A setter for every field is a public field with extra typing. The real question is which operations the class should support.
Getters and setters, used well
public class Profile {
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email address");
}
this.email = email;
}
}A setter earns its place when it validates, normalises or notifies. If it only assigns, ask whether the field should be settable at all.
Protecting mutable state
public class Team {
private final List<String> members = new ArrayList<>();
public void add(String member) {
members.add(member);
}
// Leaks the internal list: a caller can clear it
public List<String> getMembersUnsafe() {
return members;
}
// Safe: the caller gets a read only view
public List<String> getMembers() {
return Collections.unmodifiableList(members);
}
}Returning an internal collection quietly hands out write access. Return an unmodifiable view or a defensive copy instead. The same applies to arrays, dates and any other mutable object held as a field.
Encapsulation compared with abstraction
| Encapsulation | Abstraction |
|---|---|
| Hides the data and the mechanism | Hides the complexity behind a simpler idea |
| Achieved with access modifiers | Achieved with interfaces and abstract classes |
| Answers "who may touch this?" | Answers "what does this offer?" |
| An implementation concern | A design concern |
They work together: abstraction chooses the surface, encapsulation defends it.
Records and immutability
public record Money(String currency, long minorUnits) {
public Money {
if (minorUnits < 0) {
throw new IllegalArgumentException("Amount must not be negative");
}
}
}A record exposes its components but is immutable, so there is no state to corrupt. Validation still lives in the compact constructor. For pure data carriers this is often better encapsulation than a mutable class with getters and setters.
Common mistakes
- Generating a getter and a setter for every field automatically.
- Returning the internal collection or array from a getter.
- Storing a mutable object passed in by a caller without copying it.
- Making a field
protectedso a subclass can use it directly, which extends the problem rather than solving it. - Believing
privatealone is enough when the field points to something mutable.
Best practices
- Start every field
private finaland relax only with a reason. - Expose behaviour, not data:
account.withdraw(500)rather thanaccount.setBalance(...). - Copy mutable objects on the way in and on the way out.
- Use records for immutable data carriers.
- Judge a class by whether it can be broken from outside, not by how many accessors it has.
Practice
- Rewrite a class with public fields so its rules cannot be broken from outside.
- Explain how a caller could empty an internal list through a careless getter, and fix it two different ways.
- Which setters in a
Studentclass are genuinely needed, and which should be constructor arguments instead? - Convert a small mutable data class into a record and list what you gained and lost.
- Why does
private final Date createdstill allow the created date to be changed?
Conclusion
Encapsulation is the class taking responsibility for its own correctness. Keep the fields private, expose operations rather than data, and never hand out a live reference to something the class depends on.