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.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Mastering Immutability in Java: Records and Lombok @Value Made Simple

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 immutable

Why 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 safely

2. 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 stable

4. 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, "技术部"))); // true

Using 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend Developmentlombokimmutabilityrecords
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.