13

I am trying to add validation to my data models in kotlin, The simple fields are easy to do using the @field annotations. But, I am struggling to do the same with collections.

I have uploaded the issue to github here

The java model is working with no issues but the kotlin version is not. I am adding both the models here.

public class JavaUser {
    @NotEmpty
    @NotNull
    @Pattern(regexp = "[a-z]*", message = "Only lower case first name")
    private String name;

    private List<
            @NotNull
            @NotEmpty
            @Pattern(regexp = "\\d{10}", message = "Only 10 digits")
                    String> phones;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getPhones() {
        return phones;
    }

    public void setPhones(List<String> phones) {
        this.phones = phones;
    }
}
data class KotlinUser(
    @field:NotEmpty
    @field:NotNull
    @field:Pattern(regexp = "[a-z]*", message = "Only lower case first name")
    val name: String,

    // Cannot use @field here, anything else we could use?
    val phones: List<
        @NotNull
        @NotEmpty
        @Pattern(regexp = "\\d{10}", message = "Only 10 digits")
        String>
)

My tests - The java test passes but the kotlin one fails

    @Test
    fun `java user validation`() {
        val javaUser = JavaUser()
        javaUser.name = "sadfjsjdfhsjdf"
        javaUser.phones = listOf("dfhgd")

        webTestClient.put().uri("/user/java")
            .body(BodyInserters.fromObject(javaUser))
            .exchange()
            .expectStatus().is4xxClientError
    }

    @Test
    fun `kotlin user validation`() {
        val kotlinUser = KotlinUser(name = "sadfjsjdfhsjdf", phones = listOf("dfhgd"))

        webTestClient.put().uri("/user/kotlin")
            .body(BodyInserters.fromObject(kotlinUser))
            .exchange()
            .expectStatus().is4xxClientError
    }

Controller

@RestController
class Controller {
    @PutMapping("/user/java")
    fun putUser(@RequestBody @Valid javaUser: JavaUser): Mono<ResponseEntity<String>> =
        Mono.just(ResponseEntity("shouldn't get this", HttpStatus.OK))

    @PutMapping("/user/kotlin")
    fun putUser(@RequestBody @Valid kotlinUser: KotlinUser): Mono<ResponseEntity<String>> =
        Mono.just(ResponseEntity("shouldn't get this", HttpStatus.OK))
}

Any help would be greatly appreciated. Thank you!

Sachin
  • 901
  • 2
  • 10
  • 23
  • Probably something wrong with your `/kotlin` endpoint – Strelok Apr 26 '19 at 15:34
  • I don't think so. I have added the controller if you'd like to check it out – Sachin Apr 26 '19 at 15:49
  • You should try to inspect the generated bytecode to see where your annotation lands (there is [an IntelliJ feature for that](https://stackoverflow.com/questions/34957430/how-to-convert-a-kotlin-source-file-to-a-java-source-file/40762755)). I bet the absence of `@field:` yields a non-annotated getter type. The thing is, I'm not sure you can get the annotations on the type parameter in Kotlin property's type to end up on the type parameter of the generated getter's type. – Joffrey Apr 26 '19 at 16:56
  • 1
    Seems it is not supported by Kotlin. The annotation's target is supposed to be [`TYPE_PARAMETER`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.annotation/-annotation-target/-t-y-p-e_-p-a-r-a-m-e-t-e-r.html) but it is `unsupported yet`. Check out [this](https://stackoverflow.com/questions/52345291/bean-validation-not-working-with-kotlin-jsr-380) question – Denis Zavedeev Apr 29 '19 at 19:55
  • Thank you, @caco3 – Sachin Apr 29 '19 at 23:19
  • which kotlin and java version are you using ? – Naor Tedgi Apr 30 '19 at 14:27
  • Kotlin 1.3.30 and java 1.8 – Sachin Apr 30 '19 at 14:56

4 Answers4

9

This is currently not supported. The Kotlin compiler currently ignores annotations on types.

See for details:

There are also issues on the Kotlin issue tracker for this:

The latter has target version 1.4.

Mathias Dpunkt
  • 11,594
  • 4
  • 45
  • 70
4

Try this:

data class KotlinUser(
    @field:NotEmpty
    @field:NotNull
    @field:Pattern(regexp = "[a-z]*", message = "Only lower case first name")
    val name: String,

    @field:Valid
    @field:NotEmpty
    val phones: List<Phone>
)

data class Phone(
    @NotBlank
    @Pattern(regexp = "\\d{10}", message = "Only 10 digits")
    val phoneNumber: String?
)
  • 1
    this is not correct answer because it requires list of objects instead of list of strings – walv Feb 10 '23 at 18:35
0

If you don't stick to Bean Validation, YAVI can be an alternative.

You can define the validation as follows:

import am.ik.yavi.builder.ValidatorBuilder
import am.ik.yavi.builder.forEach
import am.ik.yavi.builder.konstraint

data class KotlinUser(val name: String,
                      val phones: List<String>) {
    companion object {
        val validator = ValidatorBuilder.of<KotlinUser>()
                .konstraint(KotlinUser::name) {
                    notEmpty()
                            .notNull()
                            .pattern("[a-z]*").message("Only lower case first name")
                }
                .forEach(KotlinUser::phones, {
                    constraint(String::toString, "value") {
                        it.notNull().notEmpty().pattern("\\d{10}").message("Only 10 digits")
                    }
                })
                .build()
    }
}

Here is a YAVI version of your code.

Toshiaki Maki
  • 101
  • 1
  • 4
  • 2
    Thank you for your answer. But, If I have to write code for making the validation, why would I use a library for that? I ended up writing my own validator using combination of `@field` for simple types and standard kotlin `forEach` for collections. – Sachin May 08 '19 at 15:19
0

A workaround there is to use the @Validated from spring in your controller.

Example:

@RestController
@RequestMapping("/path")
@Validated
class ExampleController {

    @PostMapping
    fun registerReturns(@RequestBody @Valid request: List<@Valid RequestClass>) {
        println(request)
    }

}

RequestClass:

@JsonIgnoreProperties(ignoreUnknown = true)
data class RequestClass(
        @field:NotBlank
        val field1: String?,
        @field:NotBlank
        @field:NotNull
        val field2: String?)

This way Beans Validation is applied.

Eduardo Briguenti Vieira
  • 4,351
  • 3
  • 37
  • 49