Spring Validation in a Kotlin Controller

The team I’m currently working with is neck-deep in Spring, and is undergoing a major effort tied to validating everything passed into their APIs. My team also writes code in both Java and Kotlin.

I have noticed that sometimes, we as a team (myself included) don’t always understand what’s going on with all of the Spring stuff, and validation stuff, that we’re working with. Spring is a complex beast, to be sure. Nobody can possibly know everything about it, and this is not meant to be an indictment of anyone.

In order to provide a reasonably comprehensive set of examples with tests for us all to refer to, I’ve been working on a Github project that contains examples of just about everything I can think of.

  1. Simple primitive (String and int specifically) types being passed to APIs
  2. Simple objects that wrap single primitives (such as a Username that is backed by a String)
  3. Complex objects that wrap those simple objects (such as a Person that has both a Username and a PhoneNumber)
  4. Coding all of the above for HTTP methods GET, PUT, POST, and DELETE
  5. In both Java and Kotlin

The Problem

So I had written a bunch of Java code, gotten all of the tests and validations working, and then decided to move on to Kotlin so that I could build up that library of code.

Have I mentioned that I’m not a Kotlin expert?

In fact, I’d never written any Kotlin code before late 2020.

No matter, let’s forge ahead! So I’ve learned that the IDE I use, IntelliJ IDEA has a nifty little capability where, when you paste Java code into a Kotlin file, it will reformat and rewrite the code so that it’s more idiomatic Kotlin. So this Java code (I’ve removed some of it for clarity):

1
2
3
4
5
6
7
public ResponseEntity<String> validateStringPathVariable(
        @PathVariable("username")
        @Pattern(regexp = "[A-Za-z]+", message = "Username Pattern Validation Message")
        @Size(min = 2, max = 15, message = "Username Size Validation Message")
        String username) {
    return ResponseEntity.ok("Username is valid");
}

Becomes this in Kotlin:

1
2
3
4
5
6
7
fun validateStringPathVariable(
        @Pattern(regexp = "[A-Za-z]+", message = "Username Pattern Validation Message")
        @Size(min = 2, max = 15, message = "Username Size Validation Message")
        @PathVariable("username") username: String?
        ): ResponseEntity<String>? {
    return ResponseEntity.ok("Username is valid")
}

The entire point of this exercise that I’ve been working on has been to test the Validations. So to that end, I’ve written some WebMvcTest test cases that use MockMvc to test the validation functionality through the Spring controller. The happy-path tests worked just fine. But when I was attemping to test the length validation on the String being passed in? Those tests failed.

So I went to Stack Overflow and asked a question. After a week of inactivity, I put a bounty on it. After a day of no responses, I asked GeePaw for help directly through his Camerata Slack.

The gist of the question I asked is “I’m attemping to test the Validation through calls to the Spring controller, but tests that I expect to return a 400 response code are returning 200 as if the input which should be invalid is actually valid. What am I doing wrong?”

The Solution

On and off, we spent about 2 hours wrangling through different ideas, stops and starts, until we found a solution. Once we saw it/figured it out, it was so simple it’s stupid.

Make the fun validateStringPathVariable to be open as well.

That’s it (partially because the class itself was already marked open – but that’s a requirement, too.)

1
2
3
4
5
6
7
open fun validateStringPathVariable(
        @Pattern(regexp = "[A-Za-z]+", message = "Username Pattern Validation Message")
        @Size(min = 2, max = 15, message = "Username Size Validation Message")
        @PathVariable("username") username: String?
        ): ResponseEntity<String>? {
    return ResponseEntity.ok("Username is valid")
}

Alternative Solution

As pointed out by Nayan Hajratwala, there’s an alternative solution to this problem. Use the org.jetbrains.kotlin.plugin.spring plugin in your build.gradle file. This (apparently) makes all Controller classes and methods open by default.

This is mentioned at the Kotlin Language documentation, too.