Part 33: Java 14 – Records – Eliminating Boilerplate While Building Better Enterprise Models

Introduction

If Java developers were asked one question for the last twenty years, it would probably be:

“Why do I have to write so much boilerplate code?”

Consider a simple DTO.

Before Java Records, creating this class required writing:

  • Fields
  • Constructor
  • Getters
  • equals()
  • hashCode()
  • toString()

Even though the class often contained no business logic whatsoever.

Example:

public class CustomerDto {

    private final Long id;

    private final String name;

    private final String email;

    public CustomerDto(Long id,
                       String name,
                       String email){

        this.id = id;
        this.name = name;
        this.email = email;

    }

    public Long getId(){

        return id;

    }

    public String getName(){

        return name;

    }

    public String getEmail(){

        return email;

    }

    @Override
    public boolean equals(...)

    @Override
    public int hashCode()

    @Override
    public String toString()

}

Nearly 70 lines of code.

Only three fields.

Most enterprise applications contain hundreds or even thousands of such classes.

Java Records eliminate this boilerplate while preserving immutability and strong typing.

They allow developers to express what data is, instead of spending time writing repetitive infrastructure code.


Learning Objectives

By the end of this article, you will be able to:

  • Understand why Records were introduced.
  • Learn how Records work.
  • Understand immutability.
  • Create constructors and validation.
  • Use Records with Spring Boot.
  • Use Records with Jackson.
  • Design REST APIs using Records.
  • Understand why Records should not replace JPA entities.

Before Java Records

Typical DTO:

public class EmployeeDto {

    private final Long id;

    private final String name;

    private final String department;

    ...

}

Most of the code exists only because Java requires it.


Java Records

The same class becomes:

public record EmployeeDto(

        Long id,

        String name,

        String department){

}

Three lines.

Exactly the same meaning.

Far less code.


What Does the Compiler Generate?

The compiler automatically creates:

  • Private final fields
  • Canonical constructor
  • Accessor methods
  • equals()
  • hashCode()
  • toString()

For example:

EmployeeDto dto =
        new EmployeeDto(1L,
                        "Rahul",
                        "Engineering");

System.out.println(dto.name());

Notice:

dto.name()

not

dto.getName()

Accessor methods have the same name as the component.


Records Are Immutable

This is one of their biggest strengths.

EmployeeDto dto =
        new EmployeeDto(...);

dto.name = "John";

Compilation error.

State cannot be modified after construction.

This makes Records naturally thread-safe when their components are also immutable.


Compact Constructors

Validation can still be performed.

public record Customer(

        Long id,

        String name){

    public Customer{

        if(name == null ||

           name.isBlank()){

            throw new IllegalArgumentException(
                    "Name cannot be blank");

        }

    }

}

No field assignments are necessary.

The compiler performs them automatically after validation.


Additional Methods

Records are not limited to data.

Business helper methods are allowed.

public record Money(

        BigDecimal amount,

        String currency){

    public boolean isPositive(){

        return amount.signum() > 0;

    }

}

Records vs POJOs

FeaturePOJORecord
ConstructorManualGenerated
GettersManualGenerated
equals/hashCodeManualGenerated
toStringManualGenerated
MutableUsuallyNo
BoilerplateHighMinimal

Enterprise Example – REST Request

public record CreateCustomerRequest(

        String firstName,

        String lastName,

        String email){

}

Spring Boot automatically binds JSON into Records.

Example request:

{
  "firstName":"Rahul",
  "lastName":"Mittal",
  "email":"rahul@example.com"
}

No setters required.


REST Response

public record CustomerResponse(

        Long id,

        String fullName,

        String email){

}

Records make excellent response models.


Kafka Events

public record OrderCreatedEvent(

        UUID orderId,

        Instant createdAt,

        BigDecimal amount){

}

Events should be immutable.

Records are a natural fit.


Configuration Objects

Spring Boot configuration:

public record StorageProperties(

        String bucket,

        String region){

}

Configuration rarely changes after startup, making immutability desirable.


Value Objects

Example:

public record EmailAddress(

        String value){

}

Validation:

public EmailAddress{

    if(!value.contains("@")){

        throw new IllegalArgumentException();

    }

}

A Record can model a domain value with validation while remaining immutable.


Records with Jackson

Modern versions of Jackson support Records out of the box.

Serialization:

Record

↓

Jackson

↓

JSON

Deserialization:

JSON

↓

Jackson

↓

Record

No additional getters or setters are required.


Records in Spring Boot

Records work well for:

  • Request DTOs
  • Response DTOs
  • Configuration properties
  • Events
  • Projections

They reduce boilerplate while making intent explicit.


Records and JPA

This is where many developers become confused.

Consider:

@Entity
public record Customer(...){
}

Although some providers have experimental support for limited scenarios, Records are generally not suitable as JPA entities.

Why?

JPA entities typically require:

  • Mutable state
  • Lifecycle management
  • Lazy loading
  • Proxy generation
  • No-argument constructors

Records intentionally provide:

  • Final components
  • Immutability
  • Canonical constructors

These design goals conflict with traditional JPA entity requirements.

Instead:

Entity

↓

Mapper

↓

Record DTO

↓

REST API

This separation is considered a best practice.


Records vs Lombok

Many projects use Lombok.

Example:

@Data
@AllArgsConstructor
@NoArgsConstructor

Records eliminate much of this boilerplate without requiring annotation processing.

However, Lombok still provides features that Records do not, such as builders and mutable data classes.


Enterprise Architecture

Database

↓

JPA Entity

↓

Mapper

↓

Record DTO

↓

REST API

↓

Client

This is becoming a common architecture in modern Spring Boot applications.


Common Mistakes

Using Records for Entities

Entities represent mutable persistence state.

Records represent immutable data.

Choose the right abstraction.


Putting Business Services Inside Records

Records may contain helper methods, but complex business logic belongs in domain services.


Assuming Records Replace Every Class

Records are ideal for immutable data carriers.

Regular classes remain appropriate for mutable domain models and services.


Best Practices

✔ Use Records for DTOs.

✔ Use Records for Kafka events.

✔ Use Records for REST APIs.

✔ Use Records for immutable value objects.

✔ Validate data in compact constructors.

✔ Keep JPA entities as regular classes.


Interview Questions

Why were Records introduced?

To reduce boilerplate for immutable data carriers while preserving strong typing.


Are Records immutable?

Yes.

Their components are final and cannot be reassigned after construction.


Can Records contain methods?

Yes.

They can define constructors, helper methods, and implement interfaces.


Should Records replace JPA entities?

Generally no.

Records are best suited for immutable DTOs and value objects, while JPA entities are typically mutable.


What accessor method does a Record generate?

For a component named name, the generated accessor is:

name()

not

getName()

Summary

Records represent one of the most significant improvements to Java’s object model. By eliminating repetitive boilerplate, they allow developers to focus on modeling data rather than writing infrastructure code.

In enterprise applications, Records are an excellent choice for DTOs, REST contracts, messaging events, configuration objects, and immutable value types. They improve readability, reduce maintenance, and integrate seamlessly with modern frameworks such as Spring Boot and Jackson.

Understanding where Records fit—and where they do not—is essential for building clean, maintainable Java applications.


Coming Up Next

Part 34 – Java 15: Sealed Classes – Controlling Inheritance for Better Domain Modeling

We’ll explore another transformative feature that reshapes object-oriented design:

  • Why unrestricted inheritance can be problematic
  • sealed, non-sealed, and final
  • Domain-driven design
  • Payment and workflow hierarchies
  • Pattern Matching synergy
  • Spring Boot considerations
  • Sealed Classes vs Interfaces
  • Enterprise architecture best practices

This article will lay the foundation for the Pattern Matching features introduced in Java 17–21, where modern Java’s type system becomes significantly more expressive.

Leave a Reply

Your email address will not be published. Required fields are marked *