Encapsulation in Java
By the Syncfusion DevTutorials team · Last updated
Protect object data with private fields, getters and setters, validation and read-only patterns, and see how encapsulation differs from data hiding.
Think about a bank locker.
Your valuables sit inside a locked box. You cannot reach in and take something out. You go to the counter, show your ID, and the bank checks it. Only then do you get access.
The bank does not stop you from using your locker. It just makes sure every visit goes through a proper check.
Encapsulation works the same way. An object locks its data away, and other code can only reach that data through the object's own methods. Those methods can check every request.
In simple words: encapsulation keeps an object's data private and lets other code use it only through safe methods.
What You Will Learn
In this lesson, you will learn:
- What encapsulation is, and why it protects your objects.
- How data hiding with
privateworks. - How access modifiers control what others can reach.
- How to write getters and setters.
- How setters can reject invalid values.
- How to make data read-only.
- The benefits of encapsulation.
- How encapsulation differs from data hiding.
Start With a Working Example
Press Run.
The account refuses a bad withdrawal
public class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount("Asha", 1000);
acc.deposit(500);
acc.withdraw(300);
acc.withdraw(5000);
acc.deposit(-200);
System.out.println("Final balance: " + acc.getBalance());
}
}
class BankAccount {
private String owner;
private int balance;
BankAccount(String owner, int balance) {
this.owner = owner;
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void deposit(int amount) {
if (amount <= 0) {
System.out.println("Deposit refused: amount must be positive.");
return;
}
balance += amount;
System.out.println("Deposited " + amount);
}
public void withdraw(int amount) {
if (amount > balance) {
System.out.println("Withdrawal refused: not enough money.");
return;
}
balance -= amount;
System.out.println("Withdrew " + amount);
}
}
Expected output:
Deposited 500
Withdrew 300
Withdrawal refused: not enough money.
Deposit refused: amount must be positive.
Final balance: 1200
balance is private. The only ways to change it are deposit and withdraw, and both check the amount first. So the balance can never go below zero, and nobody can deposit a negative amount.
Try This
In main, add the line acc.balance = 9999999;. The program no longer compiles. Outside code cannot touch the balance directly.
Introduction to Encapsulation
Encapsulation means bundling an object's data and the methods that work on that data into one class, and controlling access from outside.
It is one of the four main ideas of object-oriented programming:
Encapsulation = Protect data
Abstraction = Hide details
Inheritance = Is a
Polymorphism = Many behaviours
Without encapsulation, any code anywhere could change any field to anything. One wrong line, like student.age = -5;, would put an object into an impossible state. Finding that line in a big program is hard.
With encapsulation, every change goes through the object's own methods. The object can say "no" to bad values. So the object always stays valid.
Data Hiding
Data hiding means making fields private, so code outside the class cannot read or change them directly.
Outside code cannot reach a private field
public class Main {
public static void main(String[] args) {
Student s = new Student();
s.age = -5;
System.out.println(s.age);
}
}
class Student {
private int age;
}
The compiler reports age has private access in Student.
The impossible age -5 never gets in. The mistake is caught before the program even runs.
Hiding the data is the first step. The next step is giving outside code a safe way in.
Access Control
You already know the four access levels. In encapsulation, each one has a typical job:
| Modifier | Who can use it | Typical use in encapsulation |
|---|---|---|
private | Only this class | Fields, and helper methods |
| default (none) | Classes in the same package | Helpers shared inside one package |
protected | Same package, plus child classes | Members child classes need to customise |
public | Everyone | The methods other code is meant to call |
The usual pattern is simple:
- Fields:
private. - Methods other code needs:
public. - Internal helper methods:
private.
Easy Way to Remember: data private, actions public. Open up a member only when another class has a real need for it.
Getters and Setters
If a field is private, how does other code read or change it? Through two kinds of public methods:
- A getter returns a field's value. Its name starts with
get, such asgetName(). - A setter changes a field's value. Its name starts with
set, such assetName(String name).
For a boolean field, the getter usually starts with is, such as isActive().
Read and change private fields through methods
public class Main {
public static void main(String[] args) {
Product p = new Product();
p.setName("Wireless Mouse");
p.setPrice(899);
p.setInStock(true);
System.out.println(p.getName());
System.out.println("Price: " + p.getPrice());
System.out.println("In stock? " + p.isInStock());
}
}
class Product {
private String name;
private int price;
private boolean inStock;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public boolean isInStock() {
return inStock;
}
public void setInStock(boolean inStock) {
this.inStock = inStock;
}
}
Expected output:
Wireless Mouse
Price: 899
In stock? true
Every field is private. main reads and changes them only through the getters and setters.
At first, this can look like extra work for no gain. The gain comes in the next section: once every change goes through a setter, the setter can check it.
| Field | Getter | Setter |
|---|---|---|
private String name; | public String getName() | public void setName(String name) |
private int price; | public int getPrice() | public void setPrice(int price) |
private boolean inStock; | public boolean isInStock() | public void setInStock(boolean inStock) |
Property Validation
Validation means checking a value before accepting it. A setter is the perfect place, because it is the only door into the field.
A setter can:
- reject a bad value and keep the old one;
- correct a value, such as trimming spaces;
- throw an error, so the caller knows they made a mistake.
Setters reject values that make no sense
public class Main {
public static void main(String[] args) {
Student s = new Student();
s.setName(" Meena ");
s.setAge(16);
s.setMarks(88);
s.setAge(-3);
s.setMarks(140);
s.setName("");
System.out.println(s.getName() + ", age " + s.getAge() + ", marks " + s.getMarks());
}
}
class Student {
private String name = "Unknown";
private int age;
private int marks;
public String getName() { return name; }
public int getAge() { return age; }
public int getMarks() { return marks; }
public void setName(String name) {
if (name == null || name.isBlank()) {
System.out.println("Name refused: it cannot be empty.");
return;
}
this.name = name.trim();
}
public void setAge(int age) {
if (age < 0 || age > 120) {
System.out.println("Age refused: " + age);
return;
}
this.age = age;
}
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
System.out.println("Marks refused: " + marks);
return;
}
this.marks = marks;
}
}
Expected output:
Age refused: -3
Marks refused: 140
Name refused: it cannot be empty.
Meena, age 16, marks 88
The three bad values were all refused. The object kept its last good values.
setName also trimmed the extra spaces from " Meena ". Callers did not have to remember to do that.
Throwing an Error Instead
Printing a message is fine for learning. In real programs, a setter usually throws an exception, so the mistake cannot be ignored:
public void setAge(int age) {
if (age < 0 || age > 120) {
throw new IllegalArgumentException("Age must be 0 to 120");
}
this.age = age;
}
throw stops the current method with an error. You will learn how to handle errors in the Exception Handling chapter.
Validate in the Constructor Too
A constructor is another door into the object. If it sets fields directly, it can let bad values in. A simple fix is to have the constructor call the setters.
Student(String name, int age) {
setName(name);
setAge(age);
}
Read-Only Data Patterns
Some data should be set once and never changed. An order ID, a date of birth or an account number are good examples.
There are three common ways to make data read-only.
1. Getter With No Setter
Provide a getter, but no setter. Outside code can read the value, but cannot change it.
2. final Fields Set in the Constructor
Mark the field final. It must be set in the constructor, and it can never be changed again, even by the class's own methods.
3. A Record
A record makes every field final and read-only automatically. You learned records in the Enums lesson.
An ID that can be read but never changed
public class Main {
public static void main(String[] args) {
Order o = new Order("ORD-2041", "Asha");
System.out.println("Order ID: " + o.getId());
System.out.println("Customer: " + o.getCustomer());
o.setStatus("SHIPPED");
System.out.println("Status: " + o.getStatus());
Receipt r = new Receipt("ORD-2041", 1499);
System.out.println("Receipt: " + r.orderId() + " paid " + r.amount());
}
}
class Order {
private final String id;
private final String customer;
private String status = "PLACED";
Order(String id, String customer) {
this.id = id;
this.customer = customer;
}
public String getId() { return id; }
public String getCustomer() { return customer; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
record Receipt(String orderId, int amount) {
}
Expected output:
Order ID: ORD-2041
Customer: Asha
Status: SHIPPED
Receipt: ORD-2041 paid 1499
id and customer are final and have only getters. They are fixed for the life of the order.
status has a getter and a setter, because an order's status really does change.
Receipt is a record, so both of its values are read-only with no extra code.
Protecting Arrays and Lists
A getter that returns an array hands out a reference to the original. Outside code could then change its contents.
To stay safe, return a copy:
public int[] getMarks() {
return Arrays.copyOf(marks, marks.length);
}
Now outside code gets its own copy. Changing it does not change the object's data.
final on an array field only stops the field from pointing to a different array. The values inside the array can still be changed. Return copies from getters to keep the contents safe.
| Pattern | Who can change the value | Example |
|---|---|---|
| Getter and setter | Anyone, through the setter's checks | status |
| Getter only | Only the class's own methods | A running total |
final field with a getter | Nobody, after the constructor | An order ID |
| Record component | Nobody, ever | A receipt |
Not every field needs a setter. Add a setter only when outside code has a real reason to change the value. Fewer setters means fewer ways for an object to end up in a bad state.
Benefits of Encapsulation
Valid Objects
Setters and methods check every change. An object can never end up with a negative age or a balance below zero.
Freedom to Change the Inside
Other code only uses the public methods. You can change how data is stored, as long as the methods still work the same way.
For example, a class could switch from storing age to storing dateOfBirth. Its getAge() method would just work out the age. Nobody using the class would notice.
Easier Debugging
If a value goes wrong, there is only one place it can be changed: the setter or method. You put one check or one print statement there.
Control Over Access
You decide what is read-only, what is changeable and what is completely hidden.
Clear, Simple Use
Other programmers see a small set of public methods. They do not need to understand the inner details.
Encapsulation vs Data Hiding
The two terms are often used as if they were the same. They are closely related, but they are not identical.
- Data hiding is one technique: making fields
private, so outside code cannot reach them. - Encapsulation is the bigger idea: grouping data with the methods that manage it, and controlling access through those methods. Data hiding is one part of it.
| Data hiding | Encapsulation | |
|---|---|---|
| What it is | A technique | A design principle |
| Main focus | Keeping data out of reach | Bundling data with the methods that protect it |
| Achieved with | private fields | private fields plus public getters, setters and methods |
| Answers | "Who can see this field?" | "How should this object's data be used?" |
| Relationship | One part of encapsulation | Includes data hiding |
Think of a medicine capsule. The shell keeps the medicine hidden: that is data hiding. The capsule as a whole, a sealed package with the right dose inside, designed to be taken in one safe way, is encapsulation.
Common mistakes
The following mistakes are common when hiding fields behind getters and setters. Each row shows what you see, why it happens and how to fix it.
| Symptom | Cause | Fix |
|---|---|---|
| Fields hold impossible values | Fields are public, or setters skip checks | Make fields private and validate in setters |
| A setter seems to do nothing | name = name; without this | Write this.name = name; |
has private access | Outside code used a private field | Use the getter or setter |
cannot assign a value to final variable | A setter tried to change a final field | Remove the setter |
| An object's array changes unexpectedly | A getter returned the original array | Return a copy |
Bad values get in through new | The constructor sets fields directly | Call the setters from the constructor |
What to Remember
- Encapsulation bundles data with the methods that manage it, and controls access from outside.
- Data hiding makes fields
private. It is one part of encapsulation. - Getters read private fields. Setters change them. Boolean getters usually start with
is. - Setters are the place to validate values, so objects always stay valid.
- Make data read-only with a getter and no setter, a
finalfield, or a record. Return copies of arrays. - Encapsulation keeps objects valid, makes code easier to change and debug, and gives you control over access.
Next: Exception Handling