DEV Community

Anubhav Gupta
Anubhav Gupta

Posted on

Getters and Setters Are Not Encapsulation — Here’s What Encapsulation Actually Means

When we first learn Object-Oriented Programming, encapsulation is often explained like this:

Make fields private and access them using getters and setters.

Technically, this introduces data hiding, but it does not automatically give us good encapsulation.

There is an important difference.

class BankAccount {

    private double balance;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}
Enter fullscreen mode Exit fullscreen mode

At first glance, this looks perfectly encapsulated.

The field is private.

Nobody can directly write:

account.balance = 1000;
Enter fullscreen mode Exit fullscreen mode

Instead, they have to call:

account.setBalance(1000);
Enter fullscreen mode Exit fullscreen mode

But ask yourself one question:

What exactly did we protect?

Not much.

A caller can still do this:

account.setBalance(-50000);
Enter fullscreen mode Exit fullscreen mode

or:

account.setBalance(999999999);
Enter fullscreen mode Exit fullscreen mode

The field is private, but the object's internal state is still completely controlled by the outside world.

That is not strong encapsulation.


What Encapsulation Actually Means

Encapsulation is not just about restricting how a variable is accessed.

It is about restricting how an object's state can change.

A well-encapsulated object should:

  • protect its internal state
  • enforce business rules
  • maintain valid state
  • expose meaningful operations
  • hide unnecessary implementation details

The object itself should decide which state transitions are allowed.

This leads to an important concept:

Invariants

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

For example, suppose our banking system does not allow a balance to become negative.

Then:

balance >= 0
Enter fullscreen mode Exit fullscreen mode

is an invariant.

If we expose a generic setter:

setBalance(double balance)
Enter fullscreen mode Exit fullscreen mode

we are allowing outside code to break that invariant.

Instead of exposing the state directly, we should expose behavior.


A Better Design

Consider this version:

class BankAccount {

    private double balance;

    public BankAccount(double initialBalance) {

        if (initialBalance < 0) {
            throw new IllegalArgumentException(
                "Initial balance cannot be negative"
            );
        }

        this.balance = initialBalance;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {

        if (amount <= 0) {
            throw new IllegalArgumentException(
                "Deposit amount must be positive"
            );
        }

        balance += amount;
    }

    public void withdraw(double amount) {

        if (amount <= 0) {
            throw new IllegalArgumentException(
                "Withdrawal amount must be positive"
            );
        }

        if (amount > balance) {
            throw new IllegalStateException(
                "Insufficient balance"
            );
        }

        balance -= amount;
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice something important.

There is no setBalance() method.

Instead, the object exposes operations that make sense in its domain:

deposit()
withdraw()
getBalance()
Enter fullscreen mode Exit fullscreen mode

Now someone cannot arbitrarily change:

1000 -> 500000
Enter fullscreen mode Exit fullscreen mode

by calling:

setBalance(500000);
Enter fullscreen mode Exit fullscreen mode

They have to perform a legitimate operation.

account.deposit(500);
Enter fullscreen mode Exit fullscreen mode

This is much closer to real encapsulation.


Data Hiding vs Encapsulation

These two concepts are related, but they are not identical.

Data Hiding