Mastering Immutability in Java: Records and Lombok @Value Made Simple
This article explores how Java Records and Lombok’s @Value annotation enable developers to create immutable objects, covering the definition of immutability, its benefits for thread safety and code simplicity, traditional implementation pitfalls, and concise modern approaches with practical code examples.
In modern Java development, immutability is a key principle for building robust, thread‑safe applications. This article explains how to create immutable objects elegantly using Java Records and Lombok’s @Value annotation.
What Is an Immutable Object?
An immutable object’s state cannot change after construction. In Java it must satisfy:
Fixed state : all fields remain constant.
Final fields : each field is declared final.
No mutators : no setter or other methods that modify state.
Defensive copying : mutable references are not exposed directly.
Common Immutable Classes
Standard library examples include:
String name = "张三"; // immutable
Integer count = 100; // wrapper classes are immutable
LocalDate today = LocalDate.now(); // date‑time API is immutableWhy Immutability Matters
1. Thread safety
Immutable objects are inherently thread‑safe; multiple threads can read the same instance without synchronization.
// Safe usage in multithreaded environment
public record User(String name, int age) {}
User user = new User("李四", 25);
// Multiple threads can read user safely2. Simplified logic
Eliminating unexpected state changes makes code easier to understand and maintain.
3. Ideal map keys
Because the hash code never changes, immutable objects are perfect keys for HashMap or HashSet.
Map<User, String> userMap = new HashMap<>();
User user = new User("王五", 30);
userMap.put(user, "员工信息"); // hash remains stable4. No defensive copies
Methods can return immutable objects directly without creating defensive copies.
Traditional Way to Implement an Immutable Class
Before Java 16, developers wrote extensive boilerplate:
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override public boolean equals(Object o) { /* ... */ }
@Override public int hashCode() { /* ... */ }
@Override public String toString() { /* ... */ }
}This approach suffers from verbose code, easy mistakes, and high maintenance cost.
Excessive boilerplate.
Easy to forget final declarations.
Modifying fields requires changes in many places.
Using Java Records for Immutability
Records, introduced in Java 14, provide a concise way to define immutable data carriers.
Basic usage
public record Person(String name, int age) {}The declaration automatically generates:
Private final fields.
Public canonical constructor.
Getter methods named after the fields (e.g., name(), age()). equals(), hashCode() and toString() implementations.
Record features
public record Employee(String name, int age, String department) {
// Compact constructor for validation
public Employee {
if (age < 18) throw new IllegalArgumentException("员工年龄必须大于等于 18 岁");
if (name == null || name.isBlank()) throw new IllegalArgumentException("姓名不能为空");
}
public boolean isRetirementAge() { return age >= 60; }
public static Employee of(String name, int age) {
return new Employee(name, age, "未分配");
}
}Usage example:
Employee emp = new Employee("张三", 28, "技术部");
System.out.println(emp.name()); // 张三
System.out.println(emp); // Employee[name=张三, age=28, department=技术部]
System.out.println(emp.equals(new Employee("张三", 28, "技术部"))); // trueUsing Lombok @Value for Immutability
Lombok’s @Value annotation generates immutable classes automatically.
Adding Lombok dependency
<!-- Maven -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>Basic usage
import lombok.Value;
@Value
public class Person {
String name;
int age;
}The annotation makes the class final, fields private final, and generates an all‑args constructor, getters, equals(), hashCode(), and toString().
Advanced features
import lombok.Value;
import lombok.With;
@Value
public class Employee {
String name;
int age;
String department;
@With
String email;
public boolean isRetirementAge() { return age >= 60; }
}The @With annotation creates a withEmail(...) method that returns a new instance with the modified field.
Collections with Lombok
import lombok.Value;
import lombok.Singular;
import java.util.List;
@Value
public class Team {
String name;
@Singular
List<String> members;
}Combined with @Builder, it enables a fluent builder for immutable collections.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
