Spring boot @Valid on requestBody in controller method not working
Asked Answered
F

7

14

I am trying to validate a simple request body annotated by @Valid annotation in a @RestController annotated by @Validated. Validations are working correctly on primitive variable in request (on age in below example) but not working on pojo request body. @Valid annotation has no effect on request body Person class (i.e. controller accepting blank name and age under 18).

Person class:

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

class Person (

    @NotBlank
    val name : String,

    @Min(18)
    val age : Int
)

Controller class:

import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid

@RestController
@Validated
class HomeController {

    @GetMapping("/{age}")
    fun verifyAge(@PathVariable("age") @Min(18) age:Int): String {
        return "Eligible age."
    }

    @PostMapping
    fun personValidation(@Valid @RequestBody person : Person) : String {
        return "No validation error"
    }
}

@NotBlank, @Min and @Valid annotations came from below dependency:

    implementation("org.springframework.boot:spring-boot-starter-validation:2.3.0.RELEASE")

How to make @Valid working on Person request body in a @Validated controller?

Foreign answered 24/5, 2020 at 21:24 Comment(3)
You should declare as method param a BindingResult. after it, you'll validate like this: if(bindingResult.hasErrors()) {...}Programmer
I want to validate only using annotations and with standard validator providers like springframework or hibernate without any programmatic way, like on this example: spring.io/guides/gs/validating-form-input . I can additionally configure my project with configuration classes or dependencies but want to keep the validation clean. Also my expectations are to validate raw/primitive values in pojo classes only.Foreign
this example uses BindingResult. check it out on their github repository: github.com/spring-guides/gs-validating-form-input/blob/…Programmer
F
4

[Edited] This also worked for me (adding field annotation on constructor arguments) as answered by one of the user:

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

class Person (

    @field:NotBlank
    val name : String,

    @field:Min(18)
    val age : Int
)

Though changing the "kotlin" class definition of Person as below (constructor arguments to no-arguments) (note parentheses (...) to curly-brackets{...}) also worked without putting @field annotaion:

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

class Person {

    @NotBlank
    lateinit var name: String

    @Min(18)
    var age: Int = 0
}
Foreign answered 27/5, 2020 at 18:27 Comment(0)
T
19

Don't mark Person as a Spring @Component.

Correct way to achieve field validation is to add @field to your properties. Example code:

class SomeRequest(
    @field:NotBlank val name: String
)

This happens because Spring Boot needs this annotations to be applied on fields. Kotlin by default applied them to constructor parameter. See Annotation use-site targets on Kotlin documentation.

Triazine answered 30/5, 2020 at 10:30 Comment(2)
Thank you, sir! I searched through tons of answers and only your answer was really helpful!Rudman
I'm glad my answer helped you! :)Triazine
D
16

In my case in order for @Valid @RequestBody to work in the springboot application it was necessary to use both dependencies

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>2.0.1.Final</version>
    </dependency>
Deliverance answered 21/12, 2020 at 19:45 Comment(0)
F
4

[Edited] This also worked for me (adding field annotation on constructor arguments) as answered by one of the user:

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

class Person (

    @field:NotBlank
    val name : String,

    @field:Min(18)
    val age : Int
)

Though changing the "kotlin" class definition of Person as below (constructor arguments to no-arguments) (note parentheses (...) to curly-brackets{...}) also worked without putting @field annotaion:

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

class Person {

    @NotBlank
    lateinit var name: String

    @Min(18)
    var age: Int = 0
}
Foreign answered 27/5, 2020 at 18:27 Comment(0)
E
2

Please add this dependency to your pom.xml file.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
Earring answered 26/6, 2023 at 15:17 Comment(0)
O
1

Just in case if somebody is using @BasePathAwareController or @RepositoryRestController annotations. @Valid is not currently supported on @RepositoryRestController or @BasePathAwareController.

There is an open issue in Spring Data REST: https://github.com/spring-projects/spring-data-rest/issues/967

Outworn answered 29/1, 2021 at 15:23 Comment(0)
S
0

You may try this option.

Please map @PostMapping with a URL like @PostMapping("/validateBody") and try with its URL. As done on https://reflectoring.io/bean-validation-with-spring-boot/

Slingshot answered 25/5, 2020 at 6:45 Comment(1)
That's just a path in a validation example. That path could be any like "/users", "/products" depending upon project. The post mapping I have shared is working fine i.e. I am successfully getting my returned response "No validation error", just @Valid annotation is not giving any effect.Foreign
C
0

Piotr Solarski is correct (above) that it is not right to add component to the request boddy as the body will change and the creating a single object for it can become tricky later (even if it resolved the problem for OP).

In an MVC application, here's what was going wrong for me.

I added the usual stuff you have done (libraries in pom, @Valid on RequestBody etc)

What the Spring docs (and many blogs) leave it as subtle is that Spring looks for an exit point to throw the error but if that exit doesn't exist, it will reply with 404. After reading a lot especially this blog, adding this class got Spring to recognize @Valid and find an exit point to throw the error

    @RestControllerAdvice
    @Order(1) 
    public class RestControllerExceptionHandler {

    @RequestMapping(value = { "error/404" }, method = RequestMethod.GET)
    @ExceptionHandler(Exception.class)
    public String handleUnexpectedException(Exception e) {
        return "views/base/rest-error";         
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String handlemethodArgumentNotValid(MethodArgumentNotValidException exception) { //
        // TODO you can choose to return your custom object here, which will then get transformed to json/xml etc.
        return "Sorry, that was not quite right: " + exception.getMessage();
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public String handleConstraintViolation(ConstraintViolationException exception) { //
        // TODO you can choose to return your custom object here, which will then get transformed to json/xml etc.
        return "Sorry, that was not quite right: " + exception.getMessage();
    }

    
}
Comment answered 16/2, 2021 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.