Introduction to Java record
Java 14 introduced the Java record feature (as a preview) and officially introduced it in Java 16. A Java record
is a special class used to store immutable data.
📌 Key Features of Java record
:
✔ Immutable fields – Cannot be modified after creation.
✔ Auto-generated constructor, getters, toString()
, equals()
, and hashCode()
.
✔ Ideal for DTOs, API responses, and data modeling.
Declaring a Simple record
(Employee Example)
public record Employee(int id, String firstName, String lastName, String email) {}
📌 What this automatically generates:
// Equivalent traditional Java class
public final class Employee {
private final int id;
private final String firstName;
private final String lastName;
private final String email;
public Employee(int id, String firstName, String lastName, String email) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public int id() { return id; }
public String firstName() { return firstName; }
public String lastName() { return lastName; }
public String email() { return email; }
@Override
public boolean equals(Object obj) { /* Auto-generated */ }
@Override
public int hashCode() { /* Auto-generated */ }
@Override
public String toString() { /* Auto-generated */ }
}
📌 Why record
is better?
✅ No need to write constructors, getters, toString()
, equals()
, or hashCode()
.
✅ Immutable by default (No setters).
🔥 Why Use Records in Spring Boot?

✅ Common Use Cases in Spring Boot
Here are the best places to use records in a Spring Boot app:

Real-World Example: REST API with Java Records
Let’s build a simple Spring Boot app with a Product
entity and a ProductDTO
using record.
Step 1: Entity Class
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private Double price;
// Getters, Setters, Constructors (or use Lombok)
}
Step 2: DTO Using Record
public record ProductDTO(Long id, String name, String description, Double price) {}
This generates:
- A constructor with all fields
getters
(named as the fields)equals()
,hashCode()
, andtoString()
🧠 Records are implicitly final
and immutable.
✅ One line replaces 40+ lines of code!
Step 3: Mapper Utility
public class ProductMapper {
public static ProductDTO toDTO(Product product) {
return new ProductDTO(
product.getId(),
product.getName(),
product.getDescription(),
product.getPrice()
);
}
public static Product toEntity(ProductDTO dto) {
Product product = new Product();
product.setName(dto.name());
product.setDescription(dto.description());
product.setPrice(dto.price());
return product;
}
}
Step 4: Repository Layer
public interface ProductRepository extends JpaRepository<Product, Long> {
}
Step 5: Service Layer
@Service
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
public List<ProductDTO> getAllProducts() {
return repository.findAll()
.stream()
.map(ProductMapper::toDTO)
.toList();
}
public ProductDTO createProduct(ProductDTO dto) {
Product product = ProductMapper.toEntity(dto);
Product saved = repository.save(product);
return ProductMapper.toDTO(saved);
}
}
Step 6: REST Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService service;
public ProductController(ProductService service) {
this.service = service;
}
@GetMapping
public List<ProductDTO> getAllProducts() {
return service.getAllProducts();
}
@PostMapping
public ProductDTO createProduct(@RequestBody ProductDTO dto) {
return service.createProduct(dto);
}
}
💡 Using Records for Custom API Responses
public record ApiResponse<T>(String message, T data, LocalDateTime timestamp) {}
Example Usage:
@GetMapping
public ApiResponse<List<ProductDTO>> getAll() {
return new ApiResponse<>("Products fetched", service.getAllProducts(), LocalDateTime.now());
}
✅ Looks great, minimal code, and makes the API more structured.
🚀 Using Records with Spring Data Projections
You can return a record from a custom JPQL query:
public record ProductView(Long id, String name) {}
@Query("SELECT new com.example.demo.ProductView(p.id, p.name) FROM Product p")
List<ProductView> findAllProductNames();
⚠️ Things You CANNOT Do with Records

Best Practices
- ✅ Use records only when immutability makes sense (e.g., DTOs, value models).
- ❌ Don’t use records for JPA entities (JPA requires mutable fields).
- ✅ Combine records with
@RequestBody
or@ResponseBody
for cleaner REST APIs. - ✅ Use records for in-memory and read-only operations.
- ✅ Use records in unit tests for mock payloads and expected results.
Unit Test Example
@Test
void testProductDTO() {
ProductDTO dto = new ProductDTO(1L, "Book", "Java Book", 399.99);
assertEquals("Book", dto.name());
}
Frameworks and Libraries That Work Well with Records

📚 Summary
- Java records are lightweight, immutable data carriers.
- In Spring Boot, they’re perfect for DTOs, responses, and projections.
- They reduce boilerplate, improve readability, and encourage immutability.
- But avoid using them as entities or for mutable state.
🎯 Final Thoughts
Java Records bring the modern, minimalist syntax we’ve long wanted in Java. When combined with Spring Boot, they offer a clean, professional structure — especially for REST APIs, microservices, and cloud-native applications.
✅ Cleaner code
✅ Fewer bugs
✅ Easier maintenance
Still using POJOs for DTOs? It’s time to upgrade to Java Records and experience the simplicity.
Comments
Post a Comment
Leave Comment