LearnContact
Lesson 2150 min read

Encapsulation in Java

Learn Encapsulation in Java in detail, including data hiding, private fields, getters and setters, validation, controlled access, immutable classes, JavaBeans, defensive copying, real-world design, and best practices.

Introduction

Encapsulation is one of the fundamental principles of Object-Oriented Programming. It protects the internal state of an object and provides controlled ways to interact with that state.

Simple Encapsulation Example
public class BankAccount {

    private double balance;


    public double getBalance() {

        return balance;

    }


    public void deposit(double amount) {

        if (amount > 0) {

            balance += amount;

        }

    }

}

The balance field is private, so external code cannot directly change it. The BankAccount class controls how money is added through the deposit() method.

What You Will Learn
  • What encapsulation is.
  • Why encapsulation is important.
  • How encapsulation protects object state.
  • What data hiding means.
  • Problems caused by public fields.
  • How to implement encapsulation.
  • Why fields are commonly private.
  • How getter methods work.
  • How setter methods work.
  • How controlled access works.
  • How validation protects object state.
  • How constructors participate in encapsulation.
  • How to create read-only properties.
  • How to create write-only properties.
  • How to create read-write properties.
  • What computed properties are.
  • Getter and setter naming conventions.
  • How boolean getters are named.
  • How the this keyword is used.
  • How methods protect internal state.
  • What valid object state means.
  • What class invariants are.
  • The difference between encapsulation and data hiding.
  • The difference between encapsulation and abstraction.
  • The relationship between encapsulation and access modifiers.
  • What a fully encapsulated class is.
  • The difference between mutable and immutable classes.
  • How to create immutable classes.
  • Why final fields are useful.
  • What defensive copying is.
  • How mutable objects can break encapsulation.
  • How to protect arrays.
  • How to protect collections.
  • What unmodifiable views are.
  • What JavaBeans are.
  • JavaBean conventions.
  • How encapsulation works with inheritance.
  • How private fields behave in subclasses.
  • How protected access affects encapsulation.
  • How packages support encapsulation.
  • How package-private helpers are used.
  • How static state can be encapsulated.
  • How constants are exposed safely.
  • How records relate to encapsulation.
  • How encapsulation is used in large applications.
  • How to design real-world encapsulated classes.
  • Common encapsulation errors.
  • Encapsulation best practices.

What is Encapsulation?

Encapsulation is the practice of combining data and the methods that operate on that data inside a class while controlling direct access to the internal implementation.

Encapsulation Concept
CLASS

┌──────────────────────────────┐
│                              │
│     PRIVATE INTERNAL DATA    │
│                              │
│     balance                  │
│     accountNumber            │
│     accountStatus            │
│                              │
│     CONTROLLED OPERATIONS    │
│                              │
│     deposit()                │
│     withdraw()               │
│     getBalance()             │
│                              │
└──────────────────────────────┘

        EXTERNAL CODE

              │
              │
              ▼

     Uses public operations

     Cannot directly modify
     private internal state

The object becomes responsible for protecting its own state and deciding which operations are allowed.

Why Encapsulation is Important

Data Protection

Internal fields can be protected from uncontrolled external modification.

Validation

Values can be checked before they become part of the object state.

Controlled Access

A class decides which operations external code can perform.

Maintainability

Internal implementation can change without unnecessarily affecting external code.

Modularity

Each class manages its own responsibilities and internal state.

Reliability

Invalid states and accidental modifications become easier to prevent.

Real-World Analogy

Think of encapsulation like a bank account. A customer cannot directly enter the bank database and change the balance. The customer must use approved operations such as deposit, withdrawal, and transfer.

Bank Account Analogy
NOT ALLOWED

Customer
   │
   ▼
Directly change balance
from ₹10,000 to ₹1,000,000


ALLOWED

Customer
   │
   ├── deposit()
   │
   ├── withdraw()
   │
   └── transfer()
          │
          ▼
   Validation and rules
          │
          ▼
      Balance updated

The balance is protected, and all changes happen through controlled operations. An encapsulated Java object follows the same principle.

Encapsulation Structure

Basic Structure
public class Student {

    // Hidden internal state

    private String name;

    private int marks;


    // Controlled access

    public String getName() {

        return name;

    }


    public void setName(String name) {

        this.name = name;

    }


    public int getMarks() {

        return marks;

    }


    public void setMarks(int marks) {

        if (marks >= 0 && marks <= 100) {

            this.marks = marks;

        }

    }

}

Data Hiding

Data hiding means restricting direct access to the internal data of an object. In Java, private fields are commonly used for this purpose.

Hidden Data
public class Employee {

    private double salary;

}
Direct Access is Not Allowed
Employee employee =
        new Employee();


employee.salary = 50000;


// Compilation error

// salary has private access

Without Encapsulation

Poor Design
public class BankAccount {

    public double balance;

}
Uncontrolled Modification
BankAccount account =
        new BankAccount();


account.balance = 10000;


account.balance = -50000;


account.balance = 999999999;

Any code can assign any value directly. The BankAccount class has no opportunity to validate or reject invalid changes.

Problems Without Encapsulation

Public Field Problems
PUBLIC INTERNAL DATA

        │
        ├── Can be changed from anywhere
        │
        ├── Invalid values can be assigned
        │
        ├── Business rules can be bypassed
        │
        ├── Internal implementation is exposed
        │
        ├── Debugging becomes difficult
        │
        ├── Changes affect external code
        │
        └── Object state becomes unreliable

Implementing Encapsulation

A common approach to implementing encapsulation is to make fields private and provide public methods only for operations that external code should be allowed to perform.

Implementation Steps
STEP 1

Declare internal fields private


STEP 2

Provide necessary constructors


STEP 3

Provide controlled public methods


STEP 4

Validate incoming data


STEP 5

Protect mutable internal objects


STEP 6

Expose only what external code needs

Private Fields

Private fields can be accessed directly only inside the class that declares them.

Private Fields
public class Product {

    private int id;

    private String name;

    private double price;

    private int stock;

}
Why Fields are Commonly Private
  • External code cannot modify them directly.
  • The class controls how values change.
  • Validation can be applied.
  • Internal representation can change more safely.
  • Business rules can be protected.
  • Object state becomes easier to maintain.

Getter Methods

A getter method returns a value from an object. It provides controlled read access to internal state.

Getter Syntax
public DataType getPropertyName() {

    return field;

}
Getter Example
public class Student {

    private String name;


    public String getName() {

        return name;

    }

}
Using the Getter
Student student =
        new Student();


String studentName =
        student.getName();

Setter Methods

A setter method receives a value and updates object state according to the rules defined by the class.

Setter Syntax
public void setPropertyName(
        DataType value
) {

    field = value;

}
Setter Example
public class Student {

    private String name;


    public void setName(String name) {

        this.name = name;

    }

}

Complete Encapsulation Example

Student.java
public class Student {

    private int id;

    private String name;

    private int marks;


    public int getId() {

        return id;

    }


    public void setId(int id) {

        if (id > 0) {

            this.id = id;

        }

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        if (name != null &&
            !name.isBlank()) {

            this.name = name;

        }

    }


    public int getMarks() {

        return marks;

    }


    public void setMarks(int marks) {

        if (marks >= 0 &&
            marks <= 100) {

            this.marks = marks;

        }

    }

}
Main.java
public class Main {

    public static void main(
        String[] args
    ) {

        Student student =
                new Student();


        student.setId(101);

        student.setName("Aarav");

        student.setMarks(85);


        System.out.println(
            student.getId()
        );


        System.out.println(
            student.getName()
        );


        System.out.println(
            student.getMarks()
        );

    }

}

How Encapsulation Works

Encapsulation Flow
EXTERNAL CODE

student.setMarks(85)

        │
        ▼

PUBLIC METHOD

setMarks(85)

        │
        ▼

VALIDATION

Is value between
0 and 100?

        │
        ├── NO  → Reject
        │
        └── YES
              │
              ▼

PRIVATE FIELD

marks = 85

Controlled Access

Encapsulation does not mean that every private field must have a getter and setter. The class should expose only the operations that make sense.

Controlled Bank Account
public class BankAccount {

    private double balance;


    public double getBalance() {

        return balance;

    }


    public void deposit(double amount) {

        if (amount <= 0) {

            return;

        }


        balance += amount;

    }


    public boolean withdraw(double amount) {

        if (amount <= 0 ||
            amount > balance) {

            return false;

        }


        balance -= amount;

        return true;

    }

}
Do Not Expose Everything Automatically
  • A setter for balance would allow business rules to be bypassed.
  • deposit() expresses a valid business operation.
  • withdraw() can check available balance.
  • The class remains responsible for maintaining valid state.

Validation in Setter Methods

Age Validation
public class Person {

    private int age;


    public int getAge() {

        return age;

    }


    public void setAge(int age) {

        if (age >= 0 &&
            age <= 150) {

            this.age = age;

        }

    }

}

The setter prevents values outside the accepted range from becoming part of the object state.

Constructor Validation

Validation should also be applied when an object is created. Otherwise, a constructor may create an invalid object before setters are ever used.

Validated Constructor
public class Product {

    private String name;

    private double price;


    public Product(
        String name,
        double price
    ) {

        if (name == null ||
            name.isBlank()) {

            throw new IllegalArgumentException(
                "Name cannot be empty"
            );

        }


        if (price < 0) {

            throw new IllegalArgumentException(
                "Price cannot be negative"
            );

        }


        this.name = name;

        this.price = price;

    }

}

Read-Only Properties

A property can be made externally read-only by providing a getter without providing a public setter.

Read-Only Property
public class Employee {

    private final int employeeId;


    public Employee(int employeeId) {

        this.employeeId =
                employeeId;

    }


    public int getEmployeeId() {

        return employeeId;

    }

}

External code can read the employee ID but cannot change it through the public API.

Write-Only Properties

A write-only property provides a setter or another update operation without exposing the value through a getter. This is less common but useful for sensitive data.

Password Example
public class User {

    private String passwordHash;


    public void setPassword(
        String password
    ) {

        passwordHash =
                hash(password);

    }


    private String hash(
        String password
    ) {

        return "hashed-" + password;

    }

}
Sensitive Data
  • The original password is not exposed.
  • There is no getPassword() method.
  • The class controls how the password is processed.
  • Real applications should use established secure password-hashing libraries rather than simple custom logic.

Read-Write Properties

Read-Write Property
public class Student {

    private String name;


    public String getName() {

        return name;

    }


    public void setName(String name) {

        this.name = name;

    }

}

Computed Properties

A getter does not always need to return a stored field. It can calculate and return a value derived from other state.

Computed Property
public class Rectangle {

    private double width;

    private double height;


    public Rectangle(
        double width,
        double height
    ) {

        this.width = width;

        this.height = height;

    }


    public double getArea() {

        return width * height;

    }

}

Getter and Setter Naming Conventions

Naming Convention
FIELD

name


GETTER

getName()


SETTER

setName()



FIELD

salary


GETTER

getSalary()


SETTER

setSalary()
Common Convention
  • Getter methods commonly begin with get.
  • Setter methods commonly begin with set.
  • The property name commonly follows in PascalCase form.
  • Boolean properties may commonly use is instead of get.

Boolean Getter Methods

Boolean Property
public class User {

    private boolean active;


    public boolean isActive() {

        return active;

    }


    public void setActive(
        boolean active
    ) {

        this.active = active;

    }

}

The this Keyword in Encapsulation

The this keyword refers to the current object. It is commonly used when a method parameter has the same name as an instance field.

Using this
public class Student {

    private String name;


    public void setName(String name) {

        this.name = name;

    }

}
Meaning
this.name

Current object's field


name

Method parameter


this.name = name

Assign parameter value
to current object's field

Encapsulation and Constructors

Encapsulated Object Creation
public class Student {

    private final int id;

    private String name;


    public Student(
        int id,
        String name
    ) {

        if (id <= 0) {

            throw new IllegalArgumentException(
                "Invalid ID"
            );

        }


        if (name == null ||
            name.isBlank()) {

            throw new IllegalArgumentException(
                "Invalid name"
            );

        }


        this.id = id;

        this.name = name;

    }


    public int getId() {

        return id;

    }


    public String getName() {

        return name;

    }

}

Encapsulation and Methods

Good encapsulation often exposes meaningful operations instead of simple setters for every field.

Meaningful Operations
public class Order {

    private String status =
            "CREATED";


    public String getStatus() {

        return status;

    }


    public void confirm() {

        if (status.equals("CREATED")) {

            status = "CONFIRMED";

        }

    }


    public void cancel() {

        if (!status.equals("SHIPPED")) {

            status = "CANCELLED";

        }

    }

}
Better Than setStatus()
  • confirm() expresses a business action.
  • cancel() can enforce cancellation rules.
  • External code cannot assign arbitrary status values.
  • The object controls valid state transitions.

Protecting Object State

The internal values of an object together form its state. Encapsulation helps ensure that this state changes only through valid operations.

Object State
BANK ACCOUNT STATE

accountNumber

accountHolder

balance

status


VALID OPERATIONS

deposit()

withdraw()

freeze()

close()


INVALID DIRECT CHANGES

balance = -100000

status = "WHATEVER"

accountNumber = null

Valid Object State

Valid State Enforcement
public class Inventory {

    private int quantity;


    public void addStock(int amount) {

        if (amount <= 0) {

            throw new IllegalArgumentException(
                "Amount must be positive"
            );

        }


        quantity += amount;

    }


    public boolean removeStock(
        int amount
    ) {

        if (amount <= 0 ||
            amount > quantity) {

            return false;

        }


        quantity -= amount;

        return true;

    }


    public int getQuantity() {

        return quantity;

    }

}

Class Invariants

A class invariant is a condition that should remain true for every valid object after construction and after every public operation.

Examples of Invariants
BANK ACCOUNT

balance must not violate
the account rules


STUDENT

marks must remain
between 0 and 100


PRODUCT

price must not
be negative


INVENTORY

quantity must not
be negative


DATE RANGE

start date must not
come after end date

Encapsulation vs Data Hiding

Comparison
ENCAPSULATION

Broader design principle

Bundles state and behavior

Controls interaction

Protects implementation

Defines public API



DATA HIDING

Restricts direct access

Commonly uses private members

Protects internal details



RELATIONSHIP

Data hiding is an important
part of encapsulation

Encapsulation vs Abstraction

Comparison
ENCAPSULATION

Focuses on protecting
internal state and implementation


QUESTION

How is internal data
protected and controlled?



ABSTRACTION

Focuses on exposing
essential behavior while hiding
unnecessary implementation details


QUESTION

What should the user
of the object need to know?

Encapsulation vs Access Modifiers

Access modifiers are language tools used to control visibility. Encapsulation is a broader design principle that uses access control together with methods, validation, invariants, and carefully designed APIs.

Relationship
ACCESS MODIFIERS

public

protected

package-private

private


        │
        ▼

TOOLS FOR CONTROLLING ACCESS


ENCAPSULATION

Design principle that uses
these tools to protect state
and implementation

Fully Encapsulated Class

A class is commonly described as fully encapsulated when its instance fields are private and access to its state is controlled through methods.

Fully Encapsulated Class
public class Employee {

    private int id;

    private String name;

    private double salary;


    public int getId() {

        return id;

    }


    public void setId(int id) {

        this.id = id;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        this.name = name;

    }


    public double getSalary() {

        return salary;

    }


    public void setSalary(
        double salary
    ) {

        if (salary >= 0) {

            this.salary = salary;

        }

    }

}

Mutable Classes

A mutable object can change its state after construction.

Mutable Class
public class Student {

    private String name;


    public Student(String name) {

        this.name = name;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        this.name = name;

    }

}
Changing State
Student student =
        new Student("Aarav");


student.setName("Vihaan");

Immutable Classes

An immutable object cannot change its observable state after construction.

Simple Immutable Class
public final class Student {

    private final int id;

    private final String name;


    public Student(
        int id,
        String name
    ) {

        this.id = id;

        this.name = name;

    }


    public int getId() {

        return id;

    }


    public String getName() {

        return name;

    }

}

Creating an Immutable Class

Common Guidelines
  • Make the class final when inheritance could break immutability.
  • Make instance fields private.
  • Make state fields final where appropriate.
  • Initialize all required state during construction.
  • Do not provide methods that modify state.
  • Do not provide setters.
  • Protect mutable objects through defensive copying.
  • Do not return mutable internal objects directly.

final Fields

Final Fields
public class User {

    private final int id;

    private final String username;


    public User(
        int id,
        String username
    ) {

        this.id = id;

        this.username = username;

    }

}

A final reference field cannot be reassigned after initialization. However, if it refers to a mutable object, that object may still change.

Defensive Copying

Defensive copying means creating copies of mutable objects when receiving or returning them so external code cannot modify internal state through shared references.

Defensive Copy Concept
WITHOUT COPY

External Code
      │
      └──────────────┐
                     ▼
               Mutable Object
                     ▲
                     │
               Internal Field


Both references point
to the same object



WITH DEFENSIVE COPY

External Object
      │
      ▼
     COPY
      │
      ▼
Internal Object

The Mutable Object Problem

Broken Encapsulation
import java.util.ArrayList;

import java.util.List;


public class Team {

    private final List<String> members;


    public Team(
        List<String> members
    ) {

        this.members = members;

    }


    public List<String> getMembers() {

        return members;

    }

}
External Modification
List<String> names =
        new ArrayList<>();


names.add("Aarav");


Team team =
        new Team(names);


names.add("Unauthorized Member");


team.getMembers()
    .clear();

The internal list can be modified through both the original constructor argument and the returned getter reference.

Defensive Copy in Constructor

Constructor Copy
import java.util.ArrayList;

import java.util.List;


public class Team {

    private final List<String> members;


    public Team(
        List<String> members
    ) {

        this.members =
                new ArrayList<>(
                    members
                );

    }

}

Defensive Copy in Getter

Getter Copy
public List<String> getMembers() {

    return new ArrayList<>(
        members
    );

}

External code receives a separate list. Modifying that list does not directly modify the internal collection.

Encapsulating Arrays

Protected Array
public class Scores {

    private final int[] values;


    public Scores(int[] values) {

        this.values =
                values.clone();

    }


    public int[] getValues() {

        return values.clone();

    }

}
Arrays are Mutable
  • A final array reference does not make array elements immutable.
  • Returning the internal array directly can expose object state.
  • Cloning can protect simple arrays from direct external modification.

Encapsulating Collections

Controlled Collection
import java.util.ArrayList;

import java.util.List;


public class Course {

    private final List<String> students =
            new ArrayList<>();


    public void addStudent(
        String student
    ) {

        if (student != null &&
            !student.isBlank()) {

            students.add(student);

        }

    }


    public boolean removeStudent(
        String student
    ) {

        return students.remove(
            student
        );

    }


    public List<String> getStudents() {

        return List.copyOf(
            students
        );

    }

}

Unmodifiable Views and Copies

Unmodifiable Copy
public List<String> getStudents() {

    return List.copyOf(
        students
    );

}

Returning an unmodifiable copy prevents callers from modifying the returned collection and avoids exposing the mutable internal list directly.

JavaBeans

A JavaBean is a Java class that follows commonly used conventions for properties and object construction. JavaBeans are frequently encountered in frameworks, tools, and older Java APIs.

JavaBean Conventions

Common JavaBean Conventions
  • The class is public.
  • Properties are commonly private.
  • A public no-argument constructor is commonly provided.
  • Properties are accessed through getter and setter methods.
  • Getter and setter names follow standard naming conventions.
  • Serializable support may be used when required.

JavaBean Example

StudentBean.java
public class StudentBean {

    private int id;

    private String name;

    private boolean active;


    public StudentBean() {

    }


    public int getId() {

        return id;

    }


    public void setId(int id) {

        this.id = id;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        this.name = name;

    }


    public boolean isActive() {

        return active;

    }


    public void setActive(
        boolean active
    ) {

        this.active = active;

    }

}

Encapsulation in Inheritance

Encapsulation remains important when inheritance is used. A parent class can protect its internal fields and expose controlled operations to subclasses.

Parent Class
public class Employee {

    private double salary;


    public double getSalary() {

        return salary;

    }


    protected void increaseSalary(
        double amount
    ) {

        if (amount > 0) {

            salary += amount;

        }

    }

}

Private Members and Subclasses

A subclass does not directly access private fields declared in its parent class. It interacts through accessible methods provided by the parent.

Subclass Access
public class Manager
        extends Employee {

    public void applyBonus() {

        increaseSalary(5000);

    }

}
Private Means Private to the Declaring Class
  • The salary field is not directly accessible in Manager.
  • The parent class retains control over salary changes.
  • The protected method provides a controlled operation.

Protected Access and Encapsulation

Protected members are more exposed than private members. Use protected access carefully because subclasses can depend directly on implementation details.

General Preference
MORE PROTECTED

private fields

controlled protected methods


LESS PROTECTED

protected mutable fields

Encapsulation and Packages

Packages provide another boundary for controlling access. Implementation details can be package-private when they should be shared only among closely related classes.

Package-Private Class
package com.programinds.payment;


class PaymentValidator {

    boolean isValid(double amount) {

        return amount > 0;

    }

}

Package-Private Helpers

Payment Package
com.programinds.payment

├── PaymentService
│
│   Public API
│
├── Payment
│
│   Public domain type
│
└── PaymentValidator

    Package-private
    implementation detail

Encapsulation and Static Fields

Encapsulated Static State
public class UserCounter {

    private static int count;


    private UserCounter() {

    }


    public static int getCount() {

        return count;

    }


    public static void increment() {

        count++;

    }

}

Static state should also be protected when uncontrolled modification would be unsafe.

Encapsulation and Constants

Public Constant
public class ApplicationConfig {

    public static final int MAX_RETRIES =
            3;


    private ApplicationConfig() {

    }

}

Immutable constants can often be safely exposed publicly. Mutable objects should not be exposed merely because their references are final.

Encapsulation and Records

Java records provide a concise way to model data carriers. Record components are private and final, and accessor methods are generated automatically.

Student Record
public record Student(
    int id,
    String name
) {

}
Using Record Accessors
Student student =
        new Student(
            101,
            "Aarav"
        );


System.out.println(
    student.id()
);


System.out.println(
    student.name()
);
Record Note
  • Record accessors are named id() and name(), not getId() and getName().
  • Record components cannot be reassigned.
  • Records provide shallow immutability.
  • Mutable objects stored inside records still require careful handling.

Encapsulation in Large Applications

In large applications, encapsulation applies at multiple levels. Individual objects protect fields, packages hide implementation classes, modules control exported packages, and application layers expose carefully designed interfaces.

Encapsulation Levels
APPLICATION

├── OBJECT LEVEL
│
│   private fields
│   controlled methods
│
├── CLASS LEVEL
│
│   public API
│   private implementation
│
├── PACKAGE LEVEL
│
│   public types
│   package-private helpers
│
├── MODULE LEVEL
│
│   exported packages
│   internal packages
│
└── ARCHITECTURE LEVEL

    service interfaces
    hidden implementation details

Layered Application Example

Application Flow
CONTROLLER

Uses public service methods

        │
        ▼

SERVICE

Enforces business rules

        │
        ▼

DOMAIN OBJECT

Protects internal state

        │
        ▼

REPOSITORY

Controls persistence operations

Bank Account Example

BankAccount.java
public class BankAccount {

    private final String accountNumber;

    private String accountHolder;

    private double balance;

    private boolean active;


    public BankAccount(
        String accountNumber,
        String accountHolder
    ) {

        if (accountNumber == null ||
            accountNumber.isBlank()) {

            throw new IllegalArgumentException(
                "Account number required"
            );

        }


        if (accountHolder == null ||
            accountHolder.isBlank()) {

            throw new IllegalArgumentException(
                "Account holder required"
            );

        }


        this.accountNumber =
                accountNumber;

        this.accountHolder =
                accountHolder;

        this.active = true;

    }


    public String getAccountNumber() {

        return accountNumber;

    }


    public String getAccountHolder() {

        return accountHolder;

    }


    public void setAccountHolder(
        String accountHolder
    ) {

        if (accountHolder != null &&
            !accountHolder.isBlank()) {

            this.accountHolder =
                    accountHolder;

        }

    }


    public double getBalance() {

        return balance;

    }


    public boolean isActive() {

        return active;

    }


    public void deposit(double amount) {

        if (!active) {

            throw new IllegalStateException(
                "Account is inactive"
            );

        }


        if (amount <= 0) {

            throw new IllegalArgumentException(
                "Amount must be positive"
            );

        }


        balance += amount;

    }


    public boolean withdraw(double amount) {

        if (!active ||
            amount <= 0 ||
            amount > balance) {

            return false;

        }


        balance -= amount;

        return true;

    }


    public void close() {

        if (balance == 0) {

            active = false;

        }

    }

}

Student Example

Student.java
public class Student {

    private final int rollNumber;

    private String name;

    private int marks;


    public Student(
        int rollNumber,
        String name
    ) {

        if (rollNumber <= 0) {

            throw new IllegalArgumentException(
                "Invalid roll number"
            );

        }


        this.rollNumber =
                rollNumber;


        setName(name);

    }


    public int getRollNumber() {

        return rollNumber;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        if (name == null ||
            name.isBlank()) {

            throw new IllegalArgumentException(
                "Name required"
            );

        }


        this.name = name;

    }


    public int getMarks() {

        return marks;

    }


    public void setMarks(int marks) {

        if (marks < 0 ||
            marks > 100) {

            throw new IllegalArgumentException(
                "Marks must be 0 to 100"
            );

        }


        this.marks = marks;

    }


    public boolean hasPassed() {

        return marks >= 40;

    }

}

Employee Example

Employee.java
public class Employee {

    private final int id;

    private String name;

    private double salary;


    public Employee(
        int id,
        String name,
        double salary
    ) {

        if (id <= 0) {

            throw new IllegalArgumentException(
                "Invalid employee ID"
            );

        }


        this.id = id;

        setName(name);

        setSalary(salary);

    }


    public int getId() {

        return id;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        if (name == null ||
            name.isBlank()) {

            throw new IllegalArgumentException(
                "Name required"
            );

        }


        this.name = name;

    }


    public double getSalary() {

        return salary;

    }


    private void setSalary(
        double salary
    ) {

        if (salary < 0) {

            throw new IllegalArgumentException(
                "Salary cannot be negative"
            );

        }


        this.salary = salary;

    }


    public void increaseSalary(
        double amount
    ) {

        if (amount <= 0) {

            throw new IllegalArgumentException(
                "Increase must be positive"
            );

        }


        salary += amount;

    }

}

Product Example

Product.java
public class Product {

    private final int id;

    private String name;

    private double price;

    private int stock;


    public Product(
        int id,
        String name,
        double price
    ) {

        if (id <= 0) {

            throw new IllegalArgumentException(
                "Invalid product ID"
            );

        }


        this.id = id;

        setName(name);

        setPrice(price);

    }


    public int getId() {

        return id;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        if (name == null ||
            name.isBlank()) {

            throw new IllegalArgumentException(
                "Product name required"
            );

        }


        this.name = name;

    }


    public double getPrice() {

        return price;

    }


    public void setPrice(double price) {

        if (price < 0) {

            throw new IllegalArgumentException(
                "Price cannot be negative"
            );

        }


        this.price = price;

    }


    public int getStock() {

        return stock;

    }


    public void addStock(int quantity) {

        if (quantity <= 0) {

            throw new IllegalArgumentException(
                "Quantity must be positive"
            );

        }


        stock += quantity;

    }


    public boolean removeStock(
        int quantity
    ) {

        if (quantity <= 0 ||
            quantity > stock) {

            return false;

        }


        stock -= quantity;

        return true;

    }

}

Shopping Cart Example

ShoppingCart.java
import java.util.ArrayList;

import java.util.List;


public class ShoppingCart {

    private final List<String> items =
            new ArrayList<>();


    public void addItem(String item) {

        if (item == null ||
            item.isBlank()) {

            throw new IllegalArgumentException(
                "Invalid item"
            );

        }


        items.add(item);

    }


    public boolean removeItem(
        String item
    ) {

        return items.remove(item);

    }


    public int getItemCount() {

        return items.size();

    }


    public List<String> getItems() {

        return List.copyOf(items);

    }


    public void clear() {

        items.clear();

    }

}

Common Encapsulation Errors

Common Errors
ENCAPSULATION ERRORS

├── Declaring All Fields Public
├── Exposing Internal State Directly
├── Creating Getter for Every Field Automatically
├── Creating Setter for Every Field Automatically
├── Using Setters Without Validation
├── Allowing Invalid Values
├── Creating Invalid Objects in Constructors
├── Forgetting Constructor Validation
├── Returning Mutable Arrays Directly
├── Returning Mutable Collections Directly
├── Storing External Mutable Objects Directly
├── Forgetting Defensive Copies
├── Assuming final Makes Objects Immutable
├── Assuming final Array Cannot Change
├── Assuming final Collection Cannot Change
├── Exposing Internal List References
├── Exposing Internal Map References
├── Exposing Sensitive Information
├── Creating getPassword()
├── Using Public Mutable Static Fields
├── Using Protected Mutable Fields Unnecessarily
├── Giving Subclasses Direct Access to Internal State
├── Bypassing Business Methods
├── Using setBalance() Instead of deposit()
├── Using setStock() Instead of addStock()
├── Using setStatus() for Arbitrary State Changes
├── Allowing Impossible State Transitions
├── Ignoring Class Invariants
├── Using Silent Validation Failures
├── Swallowing Invalid Input Without Feedback
├── Duplicating Validation Logic
├── Inconsistent Constructor and Setter Validation
├── Exposing Implementation Details
├── Depending on Internal Field Names
├── Making Implementation Classes Public Unnecessarily
├── Confusing Encapsulation with Getters and Setters
├── Confusing Encapsulation with Data Hiding
├── Confusing Encapsulation with Abstraction
├── Assuming private Alone Guarantees Good Encapsulation
├── Making Everything private Without Designing an API
├── Creating Too Many Tiny Accessor Methods
├── Using Meaningless Methods
├── Breaking Object Responsibility
├── Letting External Code Maintain Object Validity
├── Returning Internal Objects Directly
├── Accepting Mutable Objects Without Copying
├── Overusing Mutable State
├── Ignoring Immutability
├── Creating Partially Initialized Objects
├── Allowing Null When Not Valid
├── Exposing Internal Exceptions Unnecessarily
├── Using Static Global State Carelessly
├── Making Constants Mutable
├── Returning Modifiable Collections
├── Assuming Unmodifiable View Means Deep Immutability
├── Ignoring Mutable Elements Inside Collections
├── Ignoring Mutable Components Inside Records
├── Breaking Encapsulation Through Reflection-Based Design Assumptions
├── Poor Package Visibility Decisions
├── Making Helpers Public Without Need
├── Overusing protected Access
├── Not Documenting Public Behavior
└── Designing Classes as Data Bags Only

Best Practices

  • Keep internal state private by default.
  • Expose only the operations external code actually needs.
  • Do not automatically create getters and setters for every field.
  • Prefer meaningful domain operations over generic setters.
  • Use deposit() instead of setBalance() when representing a bank account.
  • Use addStock() and removeStock() instead of arbitrary stock assignment.
  • Validate all values before accepting them.
  • Apply validation during object construction.
  • Keep validation rules consistent.
  • Prevent objects from entering invalid states.
  • Identify and preserve class invariants.
  • Use constructors to establish valid initial state.
  • Use final fields for values that should not be reassigned.
  • Consider immutable classes when state changes are unnecessary.
  • Do not assume final makes referenced objects immutable.
  • Protect mutable constructor arguments.
  • Use defensive copies where shared mutable references would expose state.
  • Do not return mutable internal arrays directly.
  • Do not return mutable internal collections directly.
  • Use immutable copies when appropriate.
  • Use List.copyOf() when an immutable copy fits the design.
  • Remember that collection elements may themselves be mutable.
  • Avoid exposing passwords and other sensitive values.
  • Use write-only operations when data should not be readable.
  • Use read-only properties when values should not change externally.
  • Use computed methods for derived values.
  • Follow getter and setter naming conventions when properties are appropriate.
  • Use isProperty() for boolean properties when suitable.
  • Use this clearly when parameter and field names match.
  • Keep implementation methods private when external access is unnecessary.
  • Use package-private access for package implementation details.
  • Use protected access carefully.
  • Prefer private fields over protected mutable fields.
  • Let parent classes control their own internal state.
  • Design subclass extension points intentionally.
  • Encapsulate static mutable state.
  • Avoid public mutable static fields.
  • Expose only truly immutable constants publicly.
  • Keep classes focused on their responsibilities.
  • Place behavior near the data it protects.
  • Do not force external code to maintain internal consistency.
  • Use exceptions when invalid input should be rejected explicitly.
  • Use return values when an operation can legitimately fail.
  • Choose error handling according to the domain.
  • Document important validation rules.
  • Keep public APIs stable when possible.
  • Allow internal implementation to change independently.
  • Hide implementation classes when external access is unnecessary.
  • Use interfaces where implementation details should remain replaceable.
  • Treat encapsulation as an architectural principle.
  • Apply encapsulation at object, class, package, module, and service boundaries.
  • Do not confuse encapsulation with simply writing getters and setters.
  • Review every public member and ask whether it must be public.
  • Review every setter and ask whether arbitrary replacement is valid.
  • Review every getter and ask whether returned data exposes mutable state.
  • Prefer objects that protect themselves.
  • Keep invalid states difficult or impossible to represent.
  • Use meaningful method names that describe allowed operations.
  • Control state transitions explicitly.
  • Keep domain rules inside appropriate domain objects.
  • Use encapsulation to reduce coupling.
  • Use encapsulation to improve maintainability.
  • Use encapsulation to protect business rules.
  • Use encapsulation consistently throughout the application.

Common Misconceptions

Avoid These Misconceptions
  • Encapsulation is not only making fields private.
  • Encapsulation is not only creating getters and setters.
  • Every private field does not need a getter.
  • Every private field does not need a setter.
  • A setter without validation may provide very weak protection.
  • A public getter can still expose mutable internal state.
  • Returning a private collection directly can break encapsulation.
  • A final reference does not make the referenced object immutable.
  • A final array can still have its elements changed.
  • A final list can still be modified if the list itself is mutable.
  • Private members are not directly accessible to subclasses.
  • Protected access is broader than private access.
  • Protected mutable fields can weaken encapsulation.
  • Package-private access is useful for implementation details.
  • Encapsulation and abstraction are related but different concepts.
  • Data hiding is part of encapsulation but not the entire concept.
  • Access modifiers support encapsulation but do not guarantee good design.
  • An immutable class does not provide setters.
  • A class with final fields is not automatically deeply immutable.
  • Defensive copying may be necessary for mutable objects.
  • List.copyOf() creates an unmodifiable copy but does not deeply copy mutable elements.
  • Records provide shallow immutability, not automatic deep immutability.
  • A getter can calculate a value instead of returning a field.
  • A property can be read-only.
  • A property can be write-only.
  • A class can expose behavior without exposing internal data.
  • Business methods are often better than generic setters.
  • setBalance() is not always good object-oriented design.
  • An object should protect its own valid state.
  • External code should not be responsible for maintaining class invariants.
  • Encapsulation applies to static state too.
  • Public mutable static fields can expose global state.
  • Encapsulation can exist without JavaBean-style setters.
  • JavaBeans are a convention, not the definition of encapsulation.
  • Internal implementation can change while the public API remains stable.
  • Good encapsulation reduces unnecessary coupling.
  • Encapsulation is relevant in small and large applications.
  • Encapsulation applies beyond individual fields.
  • Packages and modules can also create encapsulation boundaries.
  • Good encapsulation is about designing controlled interactions.

Practice Exercises

Exercise 1: Student Class
  • Create private fields for roll number, name, and marks.
  • Make roll number read-only.
  • Validate the student name.
  • Allow marks only between 0 and 100.
  • Create a hasPassed() method.
Exercise 2: Bank Account
  • Create a private balance field.
  • Do not create setBalance().
  • Create deposit() with validation.
  • Create withdraw() with balance validation.
  • Create getBalance() for read access.
Exercise 3: Employee Salary
  • Create private employee fields.
  • Make employee ID read-only.
  • Validate salary during construction.
  • Create increaseSalary().
  • Do not allow direct arbitrary salary changes.
Exercise 4: Product Inventory
  • Create private product fields.
  • Validate product price.
  • Create addStock().
  • Create removeStock().
  • Prevent negative stock.
Exercise 5: Read-Only Property
  • Create a User class.
  • Create a final user ID.
  • Initialize it through the constructor.
  • Provide a getter.
  • Do not provide a setter.
Exercise 6: Write-Only Password
  • Create a private password representation.
  • Provide setPassword().
  • Do not provide getPassword().
  • Add a method to verify a supplied password.
  • Explain why exposing the password would be unsafe.
Exercise 7: Immutable Class
  • Create an immutable Address class.
  • Make the class final.
  • Make all fields private and final.
  • Initialize fields in the constructor.
  • Provide getters but no setters.
Exercise 8: Protect an Array
  • Create a class containing a private array.
  • Copy the array in the constructor.
  • Return a copy from the getter.
  • Modify the original array.
  • Verify that internal state remains protected.
Exercise 9: Protect a Collection
  • Create a Course class with a private student list.
  • Create addStudent().
  • Create removeStudent().
  • Return an unmodifiable copy.
  • Verify that callers cannot modify the internal list directly.
Exercise 10: Order State
  • Create private order status.
  • Do not create setStatus().
  • Create confirm(), ship(), deliver(), and cancel() methods.
  • Allow only valid state transitions.
  • Prevent invalid operations.

Common Interview Questions

What is encapsulation in Java?

Encapsulation is the practice of combining data and behavior inside a class while controlling access to internal state and implementation details.

How is encapsulation implemented in Java?

It is commonly implemented using private fields and controlled public, protected, or package-private methods.

Why are fields commonly private?

Private fields prevent uncontrolled direct access and allow the class to control how its state is read and modified.

What is data hiding?

Data hiding is the restriction of direct access to internal implementation data.

Are encapsulation and data hiding the same?

No. Data hiding is an important part of encapsulation, while encapsulation also includes bundling state and behavior and designing controlled interactions.

What is a getter?

A getter is a method that provides read access to a property or calculated value.

What is a setter?

A setter is a method that accepts a value and updates object state according to the rules defined by the class.

Does every field need a getter and setter?

No. A class should expose only the operations required by its design.

What is a read-only property?

It is a property that can be read externally but cannot be changed through a public setter.

What is a write-only property?

It is a property that can be supplied or updated without exposing its value through a getter.

What is a fully encapsulated class?

It commonly refers to a class whose instance fields are private and whose state is accessed through controlled methods.

What is an immutable class?

An immutable class creates objects whose observable state cannot change after construction.

Does final make an object immutable?

No. A final reference cannot be reassigned, but the referenced mutable object may still change.

What is defensive copying?

Defensive copying creates copies of mutable objects to prevent shared references from exposing internal state.

Why should mutable collections not be returned directly?

Callers could modify the collection and therefore change the internal state of the object without using controlled operations.

Can a subclass directly access private fields of its parent?

No. A subclass must use accessible methods provided by the parent class.

What is the difference between encapsulation and abstraction?

Encapsulation focuses on protecting internal state and implementation, while abstraction focuses on exposing essential behavior and hiding unnecessary details.

What is a class invariant?

A class invariant is a condition that should remain true for every valid object after construction and public operations.

Why are business methods often better than setters?

Business methods express valid operations and can enforce rules, while generic setters may allow arbitrary state changes.

How does encapsulation improve maintainability?

It reduces coupling by allowing internal implementation to change while preserving a stable external API.

Frequently Asked Questions

Is making all fields private enough for encapsulation?

No. Good encapsulation also requires carefully designed methods, validation, protected invariants, and controlled exposure of mutable state.

Should I always generate getters and setters?

No. Generate only the accessors required by the class design.

Can a getter return a calculated value?

Yes. A getter can calculate a value instead of directly returning a stored field.

Can a setter be private?

Yes. A private setter can be used internally when external code should not directly change the property.

Can constructors use setter methods?

Yes, but the design should ensure that constructor validation remains clear and that overridable methods are not called unsafely during construction.

Can an encapsulated class be mutable?

Yes. Encapsulation controls state changes; it does not require objects to be immutable.

Are immutable classes always better?

No. They are useful when state should not change, but mutable objects are appropriate when controlled state changes are part of the domain.

Why use List.copyOf()?

It can provide an unmodifiable copy so callers cannot directly modify the returned collection.

Does List.copyOf() deeply copy every object?

No. The collection structure is protected, but mutable elements inside it are not automatically deeply copied.

What should I learn after Encapsulation?

The next lesson covers Inheritance in Java.

Key Takeaways

  • Encapsulation is a fundamental Object-Oriented Programming principle.
  • Encapsulation combines data and behavior inside a class.
  • Encapsulation controls access to internal state.
  • Private fields are commonly used to hide implementation data.
  • Data hiding is an important part of encapsulation.
  • Public fields can allow uncontrolled modification.
  • Objects should protect their own valid state.
  • Getter methods provide controlled read access.
  • Setter methods provide controlled write access.
  • Not every field needs a getter.
  • Not every field needs a setter.
  • Meaningful operations are often better than generic setters.
  • deposit() can be better than setBalance().
  • addStock() can be better than setStock().
  • Validation should occur before state changes.
  • Constructors should establish valid initial state.
  • A read-only property has no public setter.
  • A write-only property has no public getter.
  • A read-write property provides controlled reading and writing.
  • A computed property can return a calculated value.
  • Getter methods commonly begin with get.
  • Setter methods commonly begin with set.
  • Boolean getters commonly use is.
  • The this keyword distinguishes fields from parameters.
  • Methods can protect business rules.
  • Object state should change only through valid operations.
  • Class invariants should remain true.
  • Encapsulation is broader than data hiding.
  • Encapsulation and abstraction are different concepts.
  • Access modifiers are tools that support encapsulation.
  • Private alone does not guarantee good encapsulation.
  • A fully encapsulated class commonly has private instance fields.
  • Mutable objects can change after construction.
  • Immutable objects cannot change observable state after construction.
  • Immutable classes commonly use private final fields.
  • Immutable classes do not provide state-changing methods.
  • final references cannot be reassigned.
  • final does not make mutable objects immutable.
  • Defensive copying protects mutable internal objects.
  • Constructor arguments can expose state through shared references.
  • Getter return values can expose state through shared references.
  • Arrays are mutable.
  • Arrays may require cloning or copying.
  • Collections should not be exposed directly when modification must be controlled.
  • List.copyOf() can create an unmodifiable copy.
  • Unmodifiable collections are not automatically deeply immutable.
  • JavaBeans commonly use private properties and public accessors.
  • JavaBean conventions are not the definition of encapsulation.
  • Subclasses cannot directly access private parent fields.
  • Protected access should be used carefully.
  • Private fields with controlled protected methods can preserve stronger encapsulation.
  • Packages can hide implementation classes.
  • Package-private access can protect internal helpers.
  • Static mutable state should also be encapsulated.
  • Public mutable static fields should generally be avoided.
  • Immutable constants can often be exposed safely.
  • Records provide concise data carriers.
  • Records provide shallow immutability.
  • Mutable record components still require careful handling.
  • Encapsulation applies at object level.
  • Encapsulation applies at class level.
  • Encapsulation applies at package level.
  • Encapsulation applies at module level.
  • Encapsulation applies at architectural boundaries.
  • Bank accounts should control deposits and withdrawals.
  • Inventory objects should prevent negative stock.
  • Orders should control valid status transitions.
  • Sensitive values should not be exposed unnecessarily.
  • Public APIs should expose required behavior rather than internal implementation.
  • Good encapsulation reduces coupling.
  • Good encapsulation improves maintainability.
  • Good encapsulation improves reliability.
  • Good encapsulation protects business rules.
  • Good encapsulation makes invalid states harder to create.
  • Good encapsulation allows internal implementation to change.
  • Encapsulation is essential for professional Java application design.

Summary

Encapsulation is the Object-Oriented Programming principle of combining state and behavior inside a class while controlling access to internal implementation details. It allows objects to protect their own state and expose only the operations that external code should perform.

Private fields are commonly used to prevent uncontrolled direct access. Getter methods can provide controlled read access, while setters and other public methods can validate values before changing object state.

Good encapsulation does not mean automatically creating getters and setters for every field. Meaningful operations such as deposit(), withdraw(), addStock(), confirm(), and cancel() often provide stronger protection because they express valid business actions.

Constructors and public methods should preserve valid object state and class invariants. Invalid values should be rejected before they become part of the object.

Mutable internal objects such as arrays and collections require special care. Defensive copying and immutable copies can prevent external code from changing internal state through shared references.

Encapsulation also works with inheritance, packages, static state, constants, immutable classes, JavaBeans, records, modules, and application architecture. It is therefore much broader than simply declaring fields private.

Well-designed encapsulation reduces coupling, protects business rules, improves maintainability, prevents invalid states, and allows internal implementation details to change without unnecessarily affecting external code.

Lesson 21 Completed
  • You understand what encapsulation is.
  • You understand why encapsulation is important.
  • You understand data hiding.
  • You understand the problems caused by public fields.
  • You can implement encapsulation.
  • You understand private fields.
  • You understand getter methods.
  • You understand setter methods.
  • You understand controlled access.
  • You can validate values in setters.
  • You understand constructor validation.
  • You understand read-only properties.
  • You understand write-only properties.
  • You understand read-write properties.
  • You understand computed properties.
  • You know getter and setter naming conventions.
  • You understand boolean getters.
  • You understand the this keyword in encapsulated classes.
  • You understand how constructors support encapsulation.
  • You understand how methods protect object state.
  • You understand valid object state.
  • You understand class invariants.
  • You know the difference between encapsulation and data hiding.
  • You know the difference between encapsulation and abstraction.
  • You understand the relationship between access modifiers and encapsulation.
  • You understand fully encapsulated classes.
  • You understand mutable classes.
  • You understand immutable classes.
  • You can create an immutable class.
  • You understand final fields.
  • You understand defensive copying.
  • You understand the mutable object problem.
  • You can create defensive copies in constructors.
  • You can create defensive copies in getters.
  • You can protect arrays.
  • You can protect collections.
  • You understand unmodifiable copies.
  • You understand JavaBeans.
  • You understand JavaBean conventions.
  • You understand encapsulation with inheritance.
  • You understand private members in subclasses.
  • You understand protected access.
  • You understand package-level encapsulation.
  • You understand package-private helpers.
  • You understand encapsulated static state.
  • You understand constants and encapsulation.
  • You understand records and encapsulation.
  • You understand encapsulation in large applications.
  • You can design an encapsulated bank account.
  • You can design an encapsulated student class.
  • You can design an encapsulated employee class.
  • You can design an encapsulated product class.
  • You can protect shopping cart collections.
  • You can identify common encapsulation errors.
  • You know encapsulation best practices.
  • You are ready to learn Inheritance in Java.
Next Lesson →

Inheritance in Java