Spring validation for Kotlin primitives

Viewed 1046

I have created simple Spring Boot project with using Kotlin 1.4.10.

I have simple DTO in the project:

data class TestRequest(
        @field:NotNull val id: Int,
        val optionalId: Int?,
        val name: String,
        val optionalName: String?,
        @field:NotNull val value: Double,
        val optionalValue: Double?,
        val nested: NestedRequest,
        val optionalNested: NestedRequest?
)

data class NestedRequest(
        @field:NotNull val nestedId: Long,
        val nestedOptionalId: Long?,
        val nestedName: String,
        val optionalNestedName: String?

)

I am wondering, what is best practice to write Kotlin DTO's and validate them?

  • From one side, Kotlin allows to mark fields as not-null, which seems to be convenient for validation.
  • From another, in case of Kotlin numeric types (Int, Long, Double etc), which seems to have default value, as Java primitives do, so checking of nullability does not work for such fields unlike string ones.

If I use @JsonProperty(required = true), nullability will be checked by Jackson and not by validator, so this approach is also incorrect.

As a result I've got a question - is there a proper way of validating Kotlin DTO's at all?

1 Answers

As you have noticed, it is hard to validate kotlin primitive types for nulability, because they have default values.

I would say that using a combination of Jackson (for nullability of primitive types) and Javax validation (stuff like min/max value) is fine.

However, if you don't want to use Jackson validation, you can validate primtive types by setting the type of the variable as nullable but annotating it as @NotNull.

For example:

import javax.validation.Valid
import javax.validation.constraints.NotNull

data class MyClass(
        @get:Valid
        @get:NotNull
        val someInt: Int?,
        val someText: String
)

Now, because the type is nullable (in this example Int?) Jackson won't insert a default value for someInt, therefore someInt is going to have a value of null. After that, when the object gets validated, an error will be thrown because the value of someInt is null.

For example, if we have the following @PostMapping:

    @PostMapping("/test")
    fun testFunction(@RequestBody @Valid data: MyClass) {
        print(data)
    }

Sending a POST request with body:

{
    "someText": "wow"
}

Will return an error like this one:

  "timestamp": "2020-10-02T15:22:53.361+00:00",
  "status": 400,
  "error": "Bad Request",
  "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void main.api.TestPublicController.myObject(main.api.MyClass): [Field error in object 'myClass' on field 'someInt': rejected value [null]; ...
Related