Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Spring annotation not called

In my Spring Boot project, I've created a custom annotation with validator extending ConstraintValidator to validate some fields in RequestBody. The annotation works fine for non-nested fields but validator is not called for nested ones.

My annotation looks like:

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [CustomValidator::class])
@Suppress("unused")
@MustBeDocumented
annotation class CustomValidation(
    val message: String = "validation failed",
    val groups: Array<KClass<*>> = [],
    val payload: Array<KClass<out Payload>> = []
)

My validator class:

@Component
class CustomValidator : ConstraintValidator<CustomValidation, String> {

    override fun isValid(field: String?, context: ConstraintValidatorContext?): Boolean {
        if (field != "example") {
            return false
        }
        return true
    }
}

It works fine in cases like this:

data class MyRequest(
// validator works perfectly here
    @JsonProperty("example") @CustomValidation val example: String? = null,
    @JsonProperty("locale") val locale: String? = null
)

but when put on the nested object, a validator is not invoked:

data class MyRequest(
    @JsonProperty("nested") val nested: NestedClass? = null,
    @JsonProperty("locale") val locale: String? = null
)

data class NestedClass(
// validator not called in that case
@JsonProperty("example") @CustomValidation val example: String? = null
)

usage of MyRequest class in my RestController:

@PostMapping("/endpoint")
    fun doSomething(
        @Valid @RequestBody myRequest: MyRequest,
        @RequestHeader(value = "token") token: String
    ): ResponseEntity<MyResponse> = ResponseEntity.ok(myService.getData(myRequest))

Any ideas about how to solve that problem? I've already tried to put @Valid annotation on nested field but it's still doesn't work

like image 559
Kacper Opyrchał Avatar asked Aug 19 '19 10:08

Kacper Opyrchał


1 Answers

Usually for validation to work in a nested class, you need to annotate the field of the parent class with the same nested class type with @Valid.

Here, for validation to work on the NestedClass class, you would need to add @Valid to the nested field of MyRequest class. And since it's part of the constructor, that should be done with Annotation Use-site Targets as below:

data class MyRequest(
    @field:Valid @JsonProperty("nested") val nested: NestedClass? = null,
    @JsonProperty("locale") val locale: String? = null
)

The reason why your @CustomValidation annotation works without using the use-site target is since it has only a single target defined with @Target(AnnotationTarget.FIELD), whereas @Valid annotation has multiple possible targets, ie @Target(value={METHOD,FIELD,CONSTRUCTOR,PARAMETER}), and hence you need to have the use-site target to tell the compiler the right target.

like image 164
Madhu Bhat Avatar answered Nov 18 '22 21:11

Madhu Bhat