If you — like me — are a sucker for reducing boilerplate and are using JSR-303 – Bean Validation (I use it as part of Spring Boot) then you may have noticed how it can help you reduce validation boilerplate code. We can define REST endpoints on our application, which consume objects, that we can have Spring Boot automatically validate if we use the @Valid
annotation.
Let’s assume we have a Person class, like below:
We’ve got this class all worked out. It has a truckload of properties and all require validation. We are going to operate under the premise that most persons will have a first name and a last name, so it can’t be empty or null. Apparently, we chose a database from the stone age — so we can store persons with first and last names shorter than 20 characters in our database.
Having all of this set up, we can define a REST endpoint to consume a person (it sounds illegal when you say this out loud) like so:
Apart from the odd storage provider, all seems to be in order. We added the parameters, we added the mapping,
we made sure to annotate the request body with @Valid
, should work fine, right? Wrong. If we were to create a person with a last name longer than 20 characters, such as “Justin Timbertimbertimberlake”, we will get an error from the database chastising us on why we are allowing persons through with unvalidated last names.
What’s wrong? A quick look at the decompiled code may shed some light on the situation:
The annotations are being added to the constructor arguments. Under normal circumstances that’d be fine, but sadly Hibernate Validator (bundled with Spring) will not process these annotations on the constructor. Unlike Java, Kotlin (data) classes allow you to define fields in the constructor definition of the class. This means they aren’t actual fields in the Kotlin class. Kotlin will generate a getter (for public), setter (for var) and backing field for the properties defined in the Kotlin class (as val/var). So how can we persuade Kotlin to help out a bit by placing the annotations on Hibernate Validators favourite location?
Annotation targets
The fix is actually rather simple. We just need to explain to Kotlin that the annotations need to go elsewhere. So, when we rewrite our code to look like the snippet below, we are instructing Kotlin to not apply the annotations to the constructor arguments, but to the generated fields.
Trying to add “Justin Timbertimbertimberlake” now, will generate a validation error (length must be between 1 and 20
), leaving us with a much happier database.
As you probably expected by now, for this scenario Kotlin defines three flavours of targets:
Good to know if you want to prevent some head scratching when applying framework annotations on our brand new Kotlin codebases.
More info on annotation targets is available in the great Kotlin language reference.