Record types for immutable data

David Kumar Jan 2026
2 tabs
package com.example.demo.dto;

import java.time.LocalDateTime;
import java.util.List;

// Simple record
public record UserDTO(
    Long id,
    String name,
    String email
) {}

// Record with validation
public record CreateUserRequest(
    String name,
    String email,
    String password
) {
    // Compact constructor for validation
    public CreateUserRequest {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
    }
}

// Record with computed fields
public record Money(double amount, String currency) {

    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(amount + other.amount, currency);
    }

    public String formatted() {
        return String.format("%s %.2f", currency, amount);
    }
}

// Record implementing interface
public interface Identifiable {
    Long getId();
}

public record Post(
    Long id,
    Long userId,
    String title,
    String content,
    LocalDateTime createdAt
) implements Identifiable {

    @Override
    public Long getId() {
        return id;
    }

    public boolean isRecent() {
        return createdAt.isAfter(LocalDateTime.now().minusDays(7));
    }
}

// Nested records
public record Order(
    Long id,
    Customer customer,
    List<LineItem> items,
    Money total
) {
    public record Customer(String name, String email) {}
    public record LineItem(String product, int quantity, Money price) {}
}

// Record with static factory method
public record ApiResponse<T>(
    boolean success,
    T data,
    String message,
    int statusCode
) {
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(true, data, "Success", 200);
    }

    public static <T> ApiResponse<T> error(String message, int statusCode) {
        return new ApiResponse<>(false, null, message, statusCode);
    }
}
2 files · java Explain with highlit

Java Records (JDK 14+) provide concise syntax for immutable data carriers. Records automatically generate constructor, getters, equals, hashCode, and toString. I use records for DTOs, value objects, and API responses. Records are final and all fields are final by default. Compact constructor syntax validates or normalizes data. Records can implement interfaces and have static methods. They work perfectly with pattern matching and sealed types. Serialization libraries like Jackson support records natively. Records reduce boilerplate compared to traditional classes, improving code clarity. They're ideal for representing pure data without behavior. Spring Boot integrates records smoothly for request/response handling. Records modernize Java code, making it more expressive and maintainable.