JavaBeans for Dummies
When working with Java one inevitably comes across the term “Java Bean”.
The term has the same problem that many software development terms have:
It is not clearly defined or gets used differently from its original definition.
“Java Beans” is a case of the latter.
TLDR
Nowadays a Java Bean is usually referred to as an object that follows these patterns:
- Has a public no-argument default constructor
- Public getters and setters (like
getName(),setName()) for all fields - Implements the
java.io.Serializableinterface
In modern Java, JavaBeans are primarily used as simple data containers.
The getter/setter convention allows libraries to automatically convert data between Java objects, JSON payloads, and database tables.
For example Jackson uses JavaBeans getters/setters to deserialise Java objects from JSON.
The name
The term “Java Beans” originates from the Java Beans specification that was introduced in 1996 when Java 1.1 was released.
It’s called a “bean” because it’s meant to follow Java’s coffee-themed naming pattern.
JavaBeans™ History
The way we use Java Beans today has very little to do with their official specification.
JavaBeans™ were originally designed to be used in visual builders.
Here is the official definition from the 1996 JavaBeans API Specification:
A Java Bean is a reusable software component that can be manipulated visually in a builder tool.
Here is a historically accurate JavaBean™ that fully satisfies the specification:
import java.io.Serializable;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
public class CounterBean implements Serializable {
// 1. Private properties
private int count;
// Initialized for event support, NOT marked transient so it survives serialization
private PropertyChangeSupport changes;
// 2. Public no-argument constructor
public CounterBean() {
this.count = 0;
this.changes = new PropertyChangeSupport(this);
}
// 3. Standard Getter
public int getCount() {
return this.count;
}
// 3. Standard Setter with property change firing
public void setCount(int newCount) {
int oldCount = this.count;
this.count = newCount;
// Manual primitive wrapping (new Integer) required in JDK 1.1
this.changes.firePropertyChange("count",
new Integer(oldCount),
new Integer(newCount));
}
// 4. Standard event listener registration for bound properties
public void addPropertyChangeListener(PropertyChangeListener l) {
this.changes.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
this.changes.removePropertyChangeListener(l);
}
}
JavaBeans allowed IBM and Borland to build Java IDEs that build on top of the JavaBeans specification, like Borland JBuilder and IBM VisualAge for Java.
In IBM VisualAge for Java you dropped beans onto a canvas in the Composition Editor and wired them together with connections, without writing the code yourself:

Selecting a bean opened a property sheet. Every entry in it comes from a getter/setter pair on the bean:

Borland JBuilder worked the same way. Its designer showed the components on the left and the bean’s properties and events on the right:


JavaBean™ vs Bean today
Today’s Java Beans are not typically used for Visual Builders anymore. They also rarely follow the official specification so they should not even be called “Java Beans”.
That’s why you often see them called simply “Beans” instead of “Java Beans”
A bean is more of a de facto specification:
- Has a public no-argument default constructor
- Public getters and setters (like
getName(),setName()) for all fields
They are used as data containers (also called data objects, domain models, or DTOs). They hold state using private fields and expose that state via public getters and setters.
The getter/setter convention allows libraries to automatically convert data between Java objects, JSON payloads, and database tables.
For example Jackson relies on JavaBeans to deserialise Java objects from JSON.
A typical example would be a Request DTO that gets used by a Framework like Spring.
Most real-world applications use Lombok to auto-generate the getters / setters to reduce the amount of code that has to be written for each data container.
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data // Generates getters, setters, toString, equals, and hashCode
@NoArgsConstructor // Generates mandatory JavaBean no-arg constructor
@AllArgsConstructor // Generates constructor with all fields
public class UserDto {
private String username;
private String email;
}
In this case Spring Boot’s @RequestBody annotation triggers the deserialisation when a HTTP request hits this endpoint. Spring Boot uses Jackson under the hood to deserialise the incoming JSON into a Java object.
package com.example.controller;
import com.example.dto.UserRegistrationDto;
import com.example.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<String> registerUser(@RequestBody UserRegistrationDto userDto) {
// The JavaBean carries user payload into the business service layer
userService.createUser(userDto);
return ResponseEntity.ok("User registered successfully: " + userDto.getUsername());
}
}
Bean vs Java Record
When you look at the examples above you can see that Java requires you to write a lot of code for simple data containers. This led to third-party solutions like Lombok to become the de-facto standard in the Java community.
Java 16 introduced Records to eliminate boilerplate for data containers.
Here is a Bean compared to a Record:
Bean (19 Lines):
package com.example;
import java.io.Serializable;
public class UserBean implements Serializable {
private String name;
private int age;
public UserBean() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Record (1 Line):
package com.example;
import java.io.Serializable;
public record UserRecord(String name, int age) implements Serializable {}
Records have a very nice short syntax that makes you wonder:
Why not always use Records instead of Beans?
The simple truth is that you cannot always use Records instead of Java Beans.
Records don’t only have a different syntax, they are also different from Java Beans in other ways.
The main difference is that Records are immutable at the language level.
This design choice makes some use cases of Java Beans impossible.
As explained above, a lot of frameworks and libraries rely on the de-facto Beans standard.
The table below outlines key use cases, which option to choose, and why.
| Use Case | Best Choice | Reason |
|---|---|---|
| Database Entities (JPA/Hibernate) | JavaBean | Requires no-arg constructor, mutability, and proxy inheritance |
| API Requests / Responses (JSON DTOs) | Record | Immutable data transfer; safe, concise, and clean |
| UI Form Models / Stateful Objects | JavaBean | In-place property mutation as fields are edited |
| Pattern Matching / Value Objects | Record | Built-in value equality (equals/hashCode) and deconstruction |
Inheritance Hierarchies (extends Base) | JavaBean | Records cannot extend or be extended by other classes |