Even though full Kotlin support is only available starting with Spring 5, writing Spring 4 applications in Kotlin is already working pretty well. You just have to know some of the specialties of this combination. At meshcloud, we have been working with Kotlin and Spring Boot 1.5, which is based on Spring 4, successfully for several months. In this post, we want to share some guidance about small stumbling blocks we encountered and how we got around them.
1. Spring components have to be open
All classes defined in Kotlin are final by default. That means you cannot overwrite the methods in child classes. As Spring uses a proxy approach to provide its components, all public methods of Spring components must be overridable in child classes. The explicit solution to this is pretty simple. You have to mark your class and all public methods with the keyword open.
open class PersonController {
@GetMapping
open fun findAll() {
…
}
}
A less obvious, but definitely much more comfortable way is using the Kotlin-Spring compiler plugin. It makes all Spring components open by default. As exceptions caused by missing open declarations can be very confusing and not indicate the actual problem, we definitely recommend using the plugin, as it will protect you from frustrating error analysis. IntelliJ detects the presence of this plugin in your gradle or maven configuration and will automatically highlight any redundant open modifiers and missing final declarations, for example for methods called from constructors.
2. Handling Autowired properties
If you are using property injection, auto conversion to Kotlin by IntelliJ results in converting these properties to nullable values.
open class PersonController {
@Autowired
private var personRepository: PersonRepository? = null
}
In order to take advantage of Kotlins support for explicit nullability, there are two solutions for refactoring this code.
1. Use lateinit vars
private lateinit var personRepository: PersonRepository
This allows making the autowired property not nullable. It tells the compiler, that the property will be set before actually accessing it. The Kotlin compiler will emit code that checks that the variable is initialized by the time it gets used and if it isn’t, it throws an exception accordingly.
2. Use constructor injection
open class PersonController(
private val personRepository: PersonRepository
){ ... }
This is our preferred solution, because it allows us to define the property as immutable, and we don’t need the @Autowired annotation anymore. If the injection fails, an exception occurs during object creation and not somewhere later in the code.
Even fields annotated with @Value for configuration data can be injected in the constructor. One thing to consider here is that Kotlin uses the $ sign for string templates. Therefore the $ in the @Value annotation needs to be escaped explicitly.
open class PersonController(
@Value(“\${mail.host}”)
private val host: String
){ ... }
3. HATEOAS ControllerLinkBuilder.methodOn and null parameters
At least with Spring 4, using the ControllerLinkBuilder has some downsides. When the controller method has parameters like a principal or an @RequestBody, you usually submit null in the methodOn call. Let’s say you at first convert the Controller to Kotlin:
open class PersonController {
@GetMapping
open fun findAllOwnedByCurrentUser(principal: Principal) {
…
}
}
In the Controller, we want our parameters to be not nullable. As long as the class using the ControllerLinkBuilder is written in Java, everything works fine and null can be submitted, because the method is not actually called. So this would be a possible solution.
As we currently want to convert all our code to Kotlin, we use the following solution for now. We actually define the parameters as nullable in the controller and enforce their existence at the beginning of the method (thanks to Kotlin’s smart casts!).
open class PersonController {
@GetMapping
open fun findAllOwnedByCurrentUser(principal: Principal) {
principal!!
...
}
}
This solution allows us to use the parameter like a not nullable parameter within the function. As soon as a better solution is available, we can easily remove this check.
As Spring 5 has native Kotlin support, we expect this issue to be solved when switching to it.
4. Default Constructor for JPA entities
Eventhough this is a JPA related topic, it is something worth mentioning in the context of Spring Boot, as many of those applications use JPA. JPA always requires a default constructor on entities. A @PersistenceConstructor
exists, that should generate this constructor for you.
@Entity
data class Person @PersistenceConstructor constructor (
val firstName: String,
val lastName: String
)
Sadly, this didn’t work for us in all cases. We switched to the solution of defining default values for all properties of an entity. That way a default constructor is available for JPA:
@Entity
open class Person (
val firstName: String = "",
val lastName: String = ""
)
By now there is also a Kotlin-JPA compiler plugin available which makes the default constructor available for JPA automatically. With this approach simple classes and data classes can be used for entities.