1. Overview

In this article, we’ll learn how to use the @Valid annotation in the Spring framework with Kotlin. Most applications require validating user input, so we’ll explore using the @Valid annotation to build robust and scalable web applications.

2. The Lifecycle of the Spring Request

When the Spring application receives a request, a DispatcherServlet processes it. This servlet routes the request to the appropriate controller method based on the URL path and HTTP method. After routing the request, the controller method is executed, and any request parameters or request body are passed as arguments to the method.

Before executing the controller method, Spring validates the request parameters and request body using the @Valid annotation.

If any validation errors occur, Spring returns an error response to the client containing a list of error messages.

3. Using the @Valid Spring Annotation in Kotlin

To use the @Valid annotation in Kotlin, we must first add the necessary dependencies to our project. We’ll need to add the spring-boot-starter-validation and jakarta.validation-api dependencies to our project’s build.gradle file:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("jakarta.validation:jakarta.validation-api:3.0.2")
}

Once we’ve added the dependencies to our project, we can use the @Valid annotation to validate data.

3.1. Writing the Rules

Let’s consider an example where we want to validate a user’s registration data. Consider the following class:

data class User (
    @field:NotBlank(message = "Name must not be blank")
    val name: String,
    @field:Min(value = 18, message = "Age must be at least 18")
    val age: Int
)

In the example above, we define a data class called User with two properties: name and age. We’ve also added validation rules using the @NotBlank and @Min annotations. Each field in the class requires the @field annotation to indicate that the annotation should be applied to the property’s backing field instead of its getter method. The @NotBlank annotation ensures that the name property is not blank, while the @Min annotation ensures that the age property is at least 18.

We also added custom messages for each rule instead of the default ones so they are clearer to the end user.

3.2. Creating the Endpoint

Now, let’s say that we have a controller method that takes a User object as a parameter:

@PostMapping("/users")
fun create(@Valid @RequestBody user: User): ResponseEntity<String> {
    // Code to create a new user
    return ResponseEntity.ok("User created successfully")
}

In this example, we have a REST endpoint called “/users” that accepts a POST request with a JSON payload containing the user’s data. We use the @RequestBody annotation to specify that the request body should be mapped to the user parameter. The @Valid annotation is used to trigger validation on the user object. This tells Spring to validate the request body using the validation rules defined in the User class.

If any validation errors are found, Spring will return a 400 Bad Request response to the client with a list of error messages regarding the rules that were not followed.

3.3. Handling the Errors

Let’s see what happens when we send a request to this controller method with invalid data. For example, let’s assume that we send the following JSON in the request body:

{
  "name": "",
  "age": 16
}

In this case, the name property is an empty string, and the age property has a value of 16. Both of these values violate the validation rules defined in the User class.

When Spring receives this request, it parses the JSON and creates a new instance of the User class with the provided values. Then, it validates this instance using the validation rules defined in the class. In this case, it finds that the name property is blank and the age property is less than 18, so it will generate an error response with the following JSON:

{
  "errors": [
    "Name must not be blank",
    "Age must be at least 18"
  ]
}

The server returns this error response to the client, indicating that the request was invalid.

Behind the scenes, Spring uses a validator to perform the validation checks. The validator is an implementation of the javax.validation.Validator interface, which is part of the Java Bean Validation API. Spring provides several validator implementations out of the box, including the Hibernate Validator, which is the default validator used by Spring.

3.4. Validating Nested Objects

Sometimes, we may need to validate nested objects within an object. Let’s take an example where we have a User class with a nested Address class:

data class User(
    @field:NotBlank(message = "Name must not be blank")
    val name: String,
    @field:Min(value = 18, message = "Age must be at least 18")
    val age: Int,
    @field:Valid val address: Address
)
data class Address(
    @field:NotBlank(message = "Street must not be blank")
    val street: String,
    @field:NotBlank(message = "City must not be blank")
    val city: String,
    @field:NotBlank(message = "State must not be blank")
    val state: String
)

To validate the nested Address object, we can use the @Valid annotation on the Address field within the User class. Here, the @Valid annotation tells Spring to validate the Address object within the User object.

3.5. Potential Drawbacks of the @Valid Annotation

While the @Valid annotation can be a powerful tool for validating user input, there are a few potential pitfalls:

  • Limited functionality: The @Valid annotation can only validate basic data types and strings. More complex validation requirements may need additional validation logic.
  • Validation errors can be unclear: When a validation error occurs, the error message returned by Spring may not be clear enough for the end user to understand the issue. To provide more informative error messages, we need to implement additional error handling.
  • Over-reliance on annotations: Over-reliance on annotations can make code difficult to read and understand, particularly for developers unfamiliar with the application.

4. Conclusion

In this article, we learned how to use the @Valid annotation in Spring with Kotlin. It’s a powerful tool for validating user input in a Spring application, and it provides a simple way to validate various data types, which improves the integrity and security of our application.

While there are some potential pitfalls to be aware of, the benefits of using the @Valid annotation often outweigh the drawbacks. Using the @Valid annotation, we can create more maintainable code, reduce boilerplate code, and improve the overall quality of our Spring applications.

As always, the implementation of all these examples and code snippets are available over on GitHub.

Comments are closed on this article!