Wednesday 17 April 2024

Developing RESTful APIs with Java Records and Spring Boot 3

In this blog post, we'll explore the ins and outs of Java records, a feature introduced in Java 14 aimed at simplifying the creation of immutable data classes. We'll cover what records are, provide examples, compare them with traditional classes, address common questions, and explore their usage in Spring applications.

 

What is a Record

Java records are a special kind of class introduced in Java 14. They are designed to be lightweight, immutable data carriers, providing a concise way to declare classes that solely hold data without any additional behaviour. Records are immutable by default, meaning their state cannot be changed once created.

 

public record Employee(int id, String name, String city, int age){}

 

In this single line of code, we define a class Employee with four components: id, name, city, and age. Records automatically generate a constructor, accessors for each component, equals(), hashCode(), and toString() methods. This compact syntax greatly reduces the amount of code we need to write and maintain.

 

Comparison with Traditional Classes

public class Employee {
    private final int id;
    private final String name;
    private final String city;
    private final int age;

    public Employee(int id, String name, String city, int age) {
        this.id = id;
        this.name = name;
        this.city = city;
        this.age = age;
    }

    // Getters for each field

    // equals(), hashCode(), and toString() methods
}

As you can see, the traditional class version of Employee requires significantly more code to achieve the same functionality. With records, the syntax is much more concise, making the code easier to read and maintain.

 

FAQs about Records:

 

Are records immutable by default?

Yes, records are immutable by default. Once created, their state cannot be changed. This immutability is enforced by making the components final, and records do not have setter methods.

 

Can records be made mutable?

No, records cannot be made mutable. They are designed to be immutable data holders. If mutability is required, it's better to use regular classes instead of records.

 

How do records handle serialization?

Records handle serialization automatically, similar to regular classes. The generated toString() method provides a string representation of the record, which can be used for serialization purposes.

 

Using Records in Spring

In Spring applications, records can be particularly useful for creating Data Transfer Objects (DTOs) used for transferring data between layers.

 

Example

public record EmployeeCreateRequestDto(String name, String city, int age) { }

public record Employee(int id, String name, String city, int age){}

@PostMapping
@Operation(summary = "Save the employee")
public ResponseEntity<Employee> save(@RequestBody EmployeeCreateRequestDto dto) {
    Employee emp = getEmployee(dto);
    EMPLOYEES.add(emp);
    return ResponseEntity.status(HttpStatus.CREATED).body(emp);

}

Find the below working application.

 

Step 1: Create new maven project ‘sprigboot3-records-as-dto’.

 

Step 2: Update pom.xml with maven dependencies.

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sample.app</groupId>
  <artifactId>sprigboot3-records-as-dto</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.0.4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Step 3: Define dtos.

 

Employee.java

package com.sample.app.dto;

public record Employee(int id, String name, String city, int age){}

EmployeeCreateRequestDto.java

package com.sample.app.dto;

public record EmployeeCreateRequestDto(String name, String city, int age) { }

Step 4: Define EmployeeController class.

 

EmployeeController.java

package com.sample.app.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.sample.app.dto.Employee;
import com.sample.app.dto.EmployeeCreateRequestDto;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

@RestController
@RequestMapping("/api/v1/employees")
@Tag(name = "employees", description = "This section contains APIs related to employees")
@CrossOrigin("*")
public class EmployeeController {
    private static final List<Employee> EMPLOYEES = new ArrayList<>();
    private static final AtomicInteger ID_COUNTER = new AtomicInteger(0);

    @PostMapping
    @Operation(summary = "Save the employee")
    public ResponseEntity<Employee> save(@RequestBody EmployeeCreateRequestDto dto) {
        Employee emp = getEmployee(dto);
        EMPLOYEES.add(emp);
        return ResponseEntity.status(HttpStatus.CREATED).body(emp);

    }

    @GetMapping
    @Operation(summary = "Get all the employees")
    public ResponseEntity<EmployeesResponse> save() {
        return ResponseEntity.status(HttpStatus.OK).body(new EmployeesResponse(EMPLOYEES));

    }

    private static Employee getEmployee(EmployeeCreateRequestDto dto) {
        return new Employee(ID_COUNTER.incrementAndGet(), dto.name(), dto.city(), dto.age());
    }

    private static record EmployeesResponse(List<Employee> emps) {

    }

}

Step 5: Define main application class.

 

App.java

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

Total project structure looks like below.



Build the project

Go to the location where pom.xml is located and execute below command.

 

mvn package

 

Upon successful execution of the command, you can see a jar file ‘springboot3-records-as-dto-0.0.1-SNAPSHOT.jar’ in the target folder.

 

Execute below command to run the application.

java -jar ./target/springboot3-records-as-dto-0.0.1-SNAPSHOT.jar

 

Open swagger url 'http://localhost:8080/swagger-ui/index.html' to experiment with the apis.

 

You can download the application from this link.



Previous                                                 Next                                                 Home

No comments:

Post a Comment