48

I have an entity:

class SomeInfo(
        @NotNull @Pattern(regexp = Constraints.EMAIL_REGEX) var value: String) {
    var id: Long? = null
}

And controller method:

@RequestMapping(value = "/some-info", method = RequestMethod.POST)
public Id create(@Valid @RequestBody SomeInfo someInfo) {
       ...
    }

@Valid annotation doesn't work.

It seems Spring needs a default parameterless constructor and fancy code above becomes in something ugly (but working) like this:

class SomeInfo() {

    constructor(value: String) {
            this.value = value
        }

        @NotNull @Pattern(regexp = Constraints.EMAIL_REGEX) 
        lateinit var value: String

        var id: Long? = null
    }

Any good practice to make it less wordy?

Thanks.

fasth
  • 1,992
  • 5
  • 22
  • 36

6 Answers6

113

Seems Spring needs these annotations to be applied to a field. But Kotlin will apply these annotations to the constructor parameter. Use field: specifier when applying an annotation to make it apply to a field. The following code should work fine for you.

class SomeInfo(
    @field:NotNull
    @field:Pattern(regexp = Constraints.EMAIL_REGEX)
    var value: String
) {
    var id: Long? = null
}
Michael
  • 52,489
  • 21
  • 132
  • 138
  • 1
    Curiously, how'd you come up with that? Reflection on the resulting data model? – Scott Jan 23 '18 at 03:07
  • 1
    Idk how Michael found this but there's some references to the issue https://jira.spring.io/browse/SPR-16297 and https://github.com/spring-projects/spring-boot/issues/11343#issuecomment-351430911 – uesports135 Sep 09 '18 at 20:19
  • 1
    Finally, I have found this solution. I'd tried so many solutions but only this works! Thank you! – AloDev Sep 12 '18 at 12:45
  • 1
    Thanks [@Micheal](https://stackoverflow.com/users/170842/michael) for this solution and description. – Riddhish Ojha Feb 05 '19 at 15:36
  • Great answer for my problem. – Caio Jun 16 '21 at 17:23
12

As an alternative to Michal's answer, annotating the getter also works.

class SomeInfo(
    @get:NotNull
    @get:Pattern(regexp = Constraints.EMAIL_REGEX)
    var value: String
) {
    var id: Long? = null
}

The annoying part is, that not using @get: or @field: will annotate the constructor parameter. This is still valid kotlin code (so you don't get an error). It's just useless in these use cases.

  • I tried it and didn't get it working. Do I need some extra configuration or it just works? – ttt Jan 25 '18 at 14:15
  • No, should work out of the box. I am using it in a spring project, so I have the spring-kotlin package which essential modifies spring classes to be automatically "open" classes and "open methods" if they are spring annotated. That might be a site effect that kicks in. – Andreas Sahlbach Jan 26 '18 at 09:51
8

If you use IntelliJ to convert Java to Kotlin, the @Valid annotation in the Spring Controller method may eventually be attached to the type, instead of the variable. This would break the validation.

For example, the convertion could result in

@PostMapping
public Id create(@RequestBody someInfo: @Valid SomeInfo) {
    ...
}

This is not validating. The @Valid has to be moved to a variable like this:

@PostMapping
public Id create(@RequestBody @Valid someInfo: SomeInfo) {
    ...
}
usr42
  • 81
  • 1
  • 3
3

Also your rest controller should be marked by @Validated annotation

1

For function validation of primitives:

@Validated
class MyClass() {
    fun myFun(@Valid @NotEmpty @Size(min = 3, max = 30) name: String) {
  }
}
Zatara7
  • 231
  • 2
  • 11
0

If you are validating email addresses you could also write it like so:

import javax.validation.constraints.Email
import javax.validation.constraints.Size

data class User(
    val id: Long,
    @field:Size(min = 2)
    val firstName: String,
    @field:Size(min = 2)
    val lastName: String,
    @field:Email
    val email: String,
   ..)
kimakunc
  • 11
  • 1