Save Time in Processing Requests (Body) in Spring Boot (Kotlin)
09-10-2025
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 ofval
, 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:
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.