Sitemap

Java OOP Encapsulation

5 min readOct 3, 2026

--

Protecting the Object

Story: Our application is growing. We have created objects, but now we discover a problem: anyone can directly change their internal data. We need to teach objects how to protect themselves.

Press enter or click to view image in full size

1. The Problem

Consider:

class BankAccount {
double balance;
}

Now outside code can do:

BankAccount account = new BankAccount();
account.balance = -500000;

Our object is now in an invalid state.

The question becomes:

How do we stop outside code from directly manipulating internal state?

The answer begins with encapsulation.

2. private

We can make the field private:

class BankAccount {
private double balance;
}

Now outside code cannot directly access:

account.balance;

The field can only be directly accessed from inside BankAccount.

3. What Is Encapsulation?

Encapsulation means:

Controlling access to an object’s internal state and behavior so that the object can protect its own rules and invariants.

It is more than simply:

“Make fields private.”

A better mental model is:

Internal State
↓
Protected
↓
Controlled Public API
↓
Valid Object State

4. Public API

Instead of allowing:

account.balance = 1000;

We expose a meaningful operation:

account.deposit(1000);

Example:

class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Amount must be positive"
);
}
balance += amount;
}
}

Now the object controls how its state changes.

5. Invariants

An invariant is a condition that should remain true for an object.

Examples:

BankAccount
balance >= 0

Student
0 <= marks <= 100
Employee
salary > 0

Encapsulation allows the class to protect these rules.

6. Getters

A getter allows controlled access to a value.

class Person {
private String name;
public String getName() {
return name;
}
}

Usage:

Person p = new Person();
System.out.println(p.getName());

7. Setters

A setter allows controlled modification.

public void setName(String name) {
this.name = name;
}

However, don’t automatically create setters for every field.

A setter is appropriate only when outside code should actually be allowed to change the value.

8. Getter Without Setter

Some values should be readable but not changeable.

Example:

class Employee {
private final int employeeId;
Employee(int employeeId) {
this.employeeId = employeeId;
}
public int getEmployeeId() {
return employeeId;
}
}

There is:

getEmployeeId()

but no:

setEmployeeId()

The ID is initialized once and then remains unchanged.

9. Behavior Is Often Better Than Setters

Consider:

account.setBalance(
account.getBalance() + 1000
);

This exposes too much of the object’s internal state.

Prefer:

account.deposit(1000);

Why?

Because deposit() represents a meaningful operation.

The object owns the logic:

public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException();
}
balance += amount;
}

The outside world says:

“Deposit 1000.”

The object decides:

“How should that happen?”

10. Tell, Don’t Ask

Compare:

account.setBalance(
account.getBalance() + amount
);

with:

account.deposit(amount);

The second is more object-oriented.

Get Amit Kumar’s stories in your inbox

Join Medium for free to get updates from this writer.

Instead of asking the object for its internal state and manipulating it externally, we tell the object what operation we want.

Mental Model

Bad
Object → give me your data
Outside → I'll manipulate it
Better
Outside → perform this operation
Object → I'll handle the rules

11. Access Modifiers

Java has four important access levels.

private

Accessible directly only within the declaring class.

private
↓
declaring class

Package-private

No modifier is specified.

String name;

Accessible within the same package.

protected

Accessible within the same package and through subclass access according to Java’s protected-access rules.

public

Broadly accessible.

12. Access Modifier Mental Model

private
↓
Only declaring class

package-private
↓
Same package
protected
↓
Same package + subclass access
public
↓
Broad access

The exact protected rules become more important when studying inheritance.

13. Encapsulation Is Not Just Private Fields

This is a common misconception:

“If I make the field private, I have achieved perfect encapsulation.”

Not necessarily.

Consider:

class BankAccount {
private double balance;
public void setBalance(double balance) {
this.balance = balance;
}
}

The field is private, but this is still possible:

account.setBalance(-500000);

The public API has allowed an invalid state.

Good encapsulation means:

Private state
+
Controlled operations
+
Validation
+
Protected invariants

14. Encapsulation vs Abstraction

These concepts are related but different.

Encapsulation

Focus:

Protect and control internal state.

Typical tools:

private
public
protected
methods

Abstraction

Focus:

Hide unnecessary complexity and expose essential concepts.

Typical tools:

abstract class
interface

Easy Memory Trick

Encapsulation protects the inside.

Abstraction simplifies the outside.

15. Immutability

An immutable object is an object whose observable state cannot be changed after construction.

Example:

final class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}

There are:

private fields
+
final fields
+
no setters

Once the object is created, its state cannot be changed through its API.

16. final Does Not Automatically Mean Immutable

Consider:

final List<String> names = new ArrayList<>();

This is not necessarily immutable.

You cannot do:

names = new ArrayList<>();

But you can still do:

names.add("Amit");

Why?

Because:

final reference
↓
cannot point to another object
referenced object
↓
can still be mutable

Remember

final reference ≠ immutable object.

17. Constructor + Encapsulation

Constructors and encapsulation work together.

class Employee {
private final int id;
Employee(int id) {
if (id <= 0) {
throw new IllegalArgumentException();
}
this.id = id;
}
}

The constructor establishes a valid initial state.

Then private state prevents uncontrolled modification.

Constructor
↓
Valid initial state

private
↓
Protect state
Methods
↓
Control future changes

18. Encapsulation in a Real Class

class BankAccount {
private final String accountNumber;
private double balance;
public BankAccount(String accountNumber) {
this.accountNumber = accountNumber;
this.balance = 0;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException();
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException();
}
if (amount > balance) {
throw new IllegalArgumentException(
"Insufficient balance"
);
}
balance -= amount;
}
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
}

Outside code cannot directly manipulate:

balance
accountNumber

Instead it uses meaningful operations.

19. Core Mental Model

Think of an object as a small protected system:

OBJECT
┌─────────────────┐
│ │
Outside │ private state │
────► │ │
│ public methods │
│ │
└─────────────────┘
│
↓
Controlled state

The object should own the rules for its own state.

Quick Revision

Encapsulation

Control access to internal state and protect object invariants.

private

Directly accessible only inside the declaring class.

Getter

Controlled way to read state.

Setter

Controlled way to modify state.

Invariant

Condition that should remain valid for an object.

Immutability

Object state cannot change after construction.

final

Prevents reassignment of a variable/reference; it does not automatically make referenced objects immutable.

Good encapsulation

Private state + meaningful public behavior + validation.

--

--

Amit Kumar
Amit Kumar

Written by Amit Kumar

Software Engineer-1 @Esko | Ex- Bizongo, Samsung, Sirovate, ByteLearn, Bikayi | IIIT Gwalior'23