Save Time in Processing Requests (Body) in Spring Boot (Kotlin)

09-10-2025

By Mario S. Pinto Miranda

Currently, our team is introducing Kotlin into a Java REST API project with Spring Boot. During this process, I've noticed that handling data serialization and validation in controllers is much more explicit, which is ideal for the functional paradigm. As the “boundary” of our API, controllers become responsible for validating external data before it enters our application's domain. This article is a summary of what I've learned and will be very useful for you to speed up development with automatic serialization and validation, in a simple and declarative way.

1. Data Class: The Natural Replacement for Java Records

In Java, we use record to create immutable classes that only contain data. In Kotlin, the equivalent is the data class. With just a few lines of code, in both, the compiler automatically generates the equals(), hashCode(), toString(), and copy() methods, perfect for the task of a DTO.

The biggest difference and a great advantage is type safety and nullability (in this article I covered this topic in detail). In Kotlin, a variable cannot be null unless you explicitly indicate it with a question mark (?). This prevents deserialization errors and NullPointerException when processing JSON.

  • val name: String → Cannot be null.

  • val name: String? → Can be null.

For more information: Check the official Kotlin documentation on data class.

2. JSON Mapping: When the Name is Different

Sometimes, the field names in the JSON you receive do not match the camelCase convention we use in Kotlin (for example, if it uses snake_case). To solve this, we use the @JsonProperty annotation from the Jackson library, which Spring uses by default for mapping.

Example:

data class Data( @JsonProperty("some_data") val someData: Double, @JsonProperty("other_data") val otherData: Double )

For more information: Check out this article JsonIgnore, JsonProperty, and JsonAlias – Data Access with Quarkus – makigas.

3. @param vs. @field: The Secret Behind Annotations

To apply the annotation correctly, we sometimes need to specify the use-site target. The difference lies in which part of the code the annotation is applied to:

  • @param: applies to the constructor parameter. It is the preferred way for immutable properties (val), as it ensures that the value is assigned only during object creation.
data class UserProfile(@param:JsonProperty("user_name") val userName: String)

In this case, @param:JsonProperty tells Jackson to use the constructor to map the user_name field from JSON to the userName property.

  • @field: applies to the underlying field that Kotlin creates internally. This is less common for DTOs and can break the immutability of val, as it allows the library to inject the value directly.
data class UserProfile(@field:JsonProperty("user_name") val userName: String)

Here, Jackson tries to access and modify the field directly, which is less safe with immutable properties.

For more information: Check the official Kotlin documentation on annotation targets.

4. Automatic and Robust Validation

Spring Boot has the capability to validate DTOs declaratively using annotations from the Jakarta Bean Validation standard.

Here are the key pieces:

  • Annotations on the DTO: You use annotations like @DecimalMin("0.0") and @DecimalMax("100.0") to define rules on the properties.

  • @Valid in the Controller: In your controller method, you place the @Valid annotation next to @RequestBody. This tells Spring to validate the DTO before executing your code.

import jakarta.validation.Valid import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController import org.springframework.http.ResponseEntity @RestController class DataController { @PostMapping("/some") fun saveData(@Valid @RequestBody data: Data): ResponseEntity<String> { // Your business code here. You know the data is valid! return ResponseEntity.ok("Data received and validated successfully!") } }

For more information: Check the Java Bean Validation guide in the documentation.

5. The Validation Flow: From Request to Bad Request

The validation process is a “middleware” that intercepts the request before it reaches your method.

  • HTTP Request: A client sends the request with a JSON body.

  • Spring Boot: Receives the request and directs it to your controller.

  • Annotations: Spring detects @RequestBody and @Valid.

  • Validation: If the JSON does not meet the rules, the Validator throws a MethodArgumentNotValidException.

  • Exception Handling: Spring Boot has a default handler that catches this exception and generates an HTTP 400 Bad Request response, informing the client about the error.

Your controller method will only execute if the data is valid, keeping your business logic clean and free of manual validations.

For a visual detail, you can see the following sequence diagram:

Validation flow sequence diagram

Conclusions

By introducing Kotlin, I've seen that it is necessary to better understand how the magic of Spring Boot works to correctly use Kotlin's data classes. Additionally, adopting the non-defensive internal perspective in the system, i.e., validating at the boundary, Java Bean Validation allows this to be done very simply and declaratively, saving development time and tests to maintain while ensuring a simpler design.