Question
Why Use Getters and Setters in Java? Accessors vs Public Fields
Question
In Java, what is the advantage of using getters and setters that only read and write a value, instead of simply exposing a public field?
For example, why would this:
public String foo;
be considered worse than this:
private String foo;
public void setFoo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
If a getter or setter does more than a simple read or write, the benefit is easy to understand. What is less clear is why plain pass-through getters and setters are often preferred, even though public fields require much less boilerplate.
Short Answer
By the end of this page, you will understand why Java developers often prefer private fields with getters and setters over public fields, even when the methods appear to do nothing extra. You will learn the core idea of encapsulation, how it protects your code from future changes, when public fields are acceptable, and how this choice affects maintainability in real projects.
Concept
In Java, the main idea behind getters and setters is encapsulation.
Encapsulation means an object should control access to its own internal state. Instead of letting other code directly read or write a field, the object exposes methods that define how that value is accessed.
At first, this may seem unnecessary when a getter just returns the field and a setter just assigns it:
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
That looks equivalent to:
public String foo;
But they are not equivalent in terms of design.
Why it matters
If you expose a public field:
- Any code can read it directly
- Any code can change it directly
- You cannot later add validation without changing all calling code
- You cannot control when it is read or written
- You tightly couple other code to your internal representation
If you expose methods instead:
- You can later add validation
- You can make the property read-only or write-only
- You can log access or trigger side effects later
- You can change internal storage without changing external code
- You preserve a stable public API
The key benefit: future flexibility
The biggest reason simple getters and setters exist is not that they do something today, but that they allow you to change behavior later without breaking other code.
For example, imagine this public field:
public String email;
Other code may do this everywhere:
user.email = "test@example.com";
If later you decide emails must be lowercase and non-null, you cannot intercept those writes unless you change every place that accesses the field.
With a setter:
private String email;
public void setEmail(String email) {
if (email == null) {
throw new IllegalArgumentException("email cannot be null");
}
this.email = email.toLowerCase();
}
All existing calling code can keep using:
user.setEmail("Test@Example.com");
The API stayed the same, but the implementation improved.
Encapsulation is about boundaries
A class should present a clear public interface and hide implementation details. Public fields expose implementation details directly. Getters and setters create a boundary between:
- what outside code is allowed to do, and
- how the class actually stores its data
That boundary is valuable even if the methods are simple at first.
Important nuance
This does not mean every field in every class must have getters and setters. In simple data carriers, immutable objects, or tiny internal-only classes, public fields may sometimes be acceptable. The main lesson is that public fields remove control, while accessors preserve it.
Mental Model
Think of a class like a building.
- A public field is like leaving the storage room door open so anyone can walk in and move things around.
- A getter is like a service window where people can ask to see something.
- A setter is like a controlled drop-off desk where items are accepted according to rules.
At first, the service window may feel unnecessary because the worker is just handing you the item with no questions asked. But later, the building can add rules:
- only allow certain items
- record who accessed them
- reject invalid deliveries
- move the storage room without changing the public-facing window
The window gives flexibility. The open door does not.
Syntax and Examples
Basic syntax
Public field
public class User {
public String name;
}
Usage:
User user = new User();
user.name = "Ava";
System.out.println(user.name);
Private field with getter and setter
public class User {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Usage:
User user = new User();
user.setName("Ava");
System.out.println(user.getName());
Beginner-friendly example: validation
Here is where the accessor version becomes clearly useful:
public class User {
private String name;
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
this.name = name;
}
public String getName() {
return name;
}
}
Now the class protects itself.
User user = new User();
user.setName("Ava"); // OK
user.setName(""); // Throws exception
Read-only access with only a getter
Sometimes a value should be visible but not directly changeable:
public class Product {
private final String id;
public Product(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
This is impossible with a writable public field unless you expose the field directly and trust every caller not to modify it.
Computed value behind a getter
A getter does not have to return a stored field directly:
public class Rectangle {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
To outside code, getArea() looks like a property-style access point, but internally it computes the result.
Step by Step Execution
Consider this class:
public class BankAccount {
private double balance;
public void setBalance(double balance) {
if (balance < 0) {
throw new IllegalArgumentException("Balance cannot be negative");
}
this.balance = balance;
}
public double getBalance() {
return balance;
}
}
And this code:
BankAccount account = new BankAccount();
account.setBalance(100.0);
System.out.println(account.getBalance());
What happens step by step
-
BankAccount account = new BankAccount();- A new
BankAccountobject is created. - Its
balancefield starts with Java's default value fordouble, which is0.0.
- A new
-
account.setBalance(100.0);- The
setBalancemethod is called. - The parameter
balancereceives the value100.0. - The method checks
if (balance < 0). - Since
100.0is not negative, the check passes. this.balance = balance;stores100.0in the object's private field.
- The
-
account.getBalance()- The getter returns the current value of the private field.
- That value is
100.0.
-
System.out.println(...)- The value
100.0is printed.
- The value
What if the field were public?
public class BankAccount {
public double balance;
}
Then this would be possible:
account.balance = -500.0;
No validation would stop it. The object could enter an invalid state.
Real World Use Cases
Getters and setters are commonly used when a class needs to protect or shape its data.
Common real-world scenarios
User input validation
user.setEmail("TEST@EXAMPLE.COM");
The setter can:
- reject
null - trim whitespace
- normalize case
- enforce format rules
Domain rules
In a payroll system:
employee.setSalary(50000);
The setter can ensure salary is not negative.
Read-only identifiers
A database entity may have an ID that should be readable but not changed freely:
order.getId();
No public setter is provided.
Derived or computed values
A getter can calculate a value instead of returning a stored field:
invoice.getTotal();
This may sum line items internally.
Lazy loading or formatting
A getter may load data only when needed or return a cleaned-up representation.
Framework compatibility
Many Java libraries and frameworks expect JavaBean-style accessors such as:
getName()setName(...)isActive()
This is common in serialization, UI binding, and dependency injection tools.
Real Codebase Usage
In real projects, developers use accessors as part of a broader design strategy.
Common patterns
Guard clauses in setters
Setters often validate early:
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.age = age;
}
This keeps invalid state out of the object.
Early returns in getters
A getter may return a fallback value:
public String getDisplayName() {
if (nickname != null && !nickname.isBlank()) {
return nickname;
}
return fullName;
}
Defensive copying
If a field is mutable, a getter may return a copy instead of the original:
public List<String> getTags() {
return new ArrayList<>(tags);
}
This prevents outside code from mutating internal state directly.
Restricting writes
Many classes expose getters but no setters for fields that should not change often.
Refactoring without breaking callers
A field may start as directly stored:
private String fullName;
Later, it may be split internally:
private String firstName;
private String lastName;
The class can still keep:
public String getFullName() {
return firstName + " " + lastName;
}
Outside code does not need to change.
Integration with libraries
Frameworks often inspect getter/setter methods using reflection. Even simple accessors can make a class easier to use with:
- JSON serializers
- ORM tools
- UI binding systems
- validation frameworks
Common Mistakes
1. Creating getters and setters for everything without thinking
Some beginners expose every field with both getter and setter automatically. That can weaken design.
For example, this class allows uncontrolled changes:
public class Account {
private double balance;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
A better design may be:
public class Account {
private double balance;
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
balance += amount;
}
}
Sometimes behavior-focused methods are better than generic setters.
2. Using public fields for important state
Broken design:
public class Temperature {
public double celsius;
}
Any code can assign impossible or inconsistent values if your class later grows more complex.
3. Forgetting validation
Broken setter:
public void setEmail(String email) {
this.email = email;
}
Safer setter:
public void setEmail(String email) {
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("Email cannot be empty");
}
this.email = email;
}
4. Returning mutable internal objects directly
Broken getter:
public List<String> getTags() {
return tags;
}
Outside code can now change tags directly.
Safer version:
public List<String> getTags() {
return new ArrayList<>(tags);
}
5. Assuming getters and setters are always free
A getter may compute a value or load data. A setter may validate or trigger updates. Do not assume they are always trivial.
6. Treating getters/setters as a substitute for good modeling
If your object has rules, create meaningful methods.
Instead of:
order.setStatus("SHIPPED");
you might prefer:
order.markAsShipped();
This can enforce business rules more clearly.
Comparisons
Public fields vs getters/setters
| Approach | Pros | Cons | Best use |
|---|---|---|---|
public field | Very simple, less boilerplate | No control, no validation, harder to change later | Tiny internal data holders, constants, simple demos |
private field + getter/setter | Encapsulation, validation, flexibility, stable API | More boilerplate | Most object-oriented application code |
Getter only vs getter and setter
| Pattern | Meaning | Use case |
|---|---|---|
| Getter only | Read-only from outside | IDs, timestamps, calculated values |
| Getter + setter | Read and write allowed | Editable properties with or without validation |
| No getter/setter | Fully internal field | Helper state not meant for public use |
Setter vs behavior method
| Method style | Example | When better |
|---|---|---|
| Generic setter | setBalance(100) | Simple property assignment |
| Behavior method | deposit(100) | When business rules matter |
Public field can still be okay sometimes
In Java, public fields are often acceptable for:
public static finalconstants- very simple immutable data carriers in limited contexts
- internal code where long-term API stability is irrelevant
But for reusable classes and evolving codebases, accessors are usually safer.
Cheat Sheet
Quick reference
Public field
public String name;
- Simple
- No validation
- No control over reads/writes
- Harder to change later without breaking callers
Private field with accessor methods
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
- Supports encapsulation
- Lets you add validation later
- Keeps internal representation hidden
- Works well with many Java frameworks
Common rules
- Use
privatefields by default - Add a getter if outside code needs to read the value
- Add a setter only if outside code should be allowed to change it
- Prefer meaningful methods like
deposit()orrenameTo()when behavior matters - Avoid exposing mutable internal objects directly
Naming conventions
getName()for most fieldsisActive()for boolean fieldssetName(String name)for writable properties
Good reasons to use accessors
- Validation
- Normalization
- Logging
- Read-only access
- Future refactoring
- Framework conventions
When public fields may be okay
- Constants:
public static final int MAX_SIZE = 100;
- Very small internal-only structures
- Short-lived demo code
Key idea
A public field exposes data. A getter/setter exposes an interface.
FAQ
Why are getters and setters better than public fields in Java?
They support encapsulation. You can control access, validate input, and change implementation later without breaking calling code.
If a getter and setter only pass through the value, are they still useful?
Yes. Their main value is preserving flexibility for future changes and hiding internal representation.
Should every private field have a getter and setter?
No. Only expose what outside code actually needs. Some fields should be read-only, and some should stay completely internal.
Are public fields ever acceptable in Java?
Yes, sometimes. Common examples are public static final constants and very simple internal data holders.
Why not just make everything public for less boilerplate?
Because it makes your code harder to protect, validate, and evolve. Saving a few lines now can create larger maintenance problems later.
Is a setter always the right way to modify object state?
No. If the change has business meaning, a behavior method like deposit(), activate(), or markAsPaid() is often better.
Do Java frameworks prefer getters and setters?
Many do. JavaBean-style methods are commonly used by serializers, ORMs, UI tools, and dependency injection frameworks.
Mini Project
Description
Build a small UserProfile class that stores a username and age. The project demonstrates why private fields and accessors are useful by enforcing basic validation rules and exposing safe read access.
Goal
Create a class that prevents invalid data while still allowing other code to read and update allowed values through methods.
Requirements
- Create a
UserProfileclass with private fields forusernameandage. - Add getter methods for both fields.
- Add setter methods that reject an empty username and a negative age.
- In a
mainmethod, create aUserProfileobject and print its values. - Try at least one invalid update and handle the error.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.