DEV Community

Jonas Tonndorf-Martini
Jonas Tonndorf-Martini

Posted on • Originally published at Medium

Current State of Spring Boot 2.7 Native with Kotlin (GraalVM)

This article covers the experience of setting up a sample Spring Boot 2.7 project with Kotlin 1.7 and then compiling it to a native platform executable with the help of GraalVM native-image and the Spring AOT (Ahead-of-time) plugin.

Image description

Why

The JVM (Java Virtual Machine) that is used to run most server-side Kotlin programs is known for a slow start-up and warm-up time before being able to deliver peak performance.

Especially in fast-scaling cloud environments, this can be a major downside. Additionally, the deployment size of a server program can get quite huge. GraalVM is provided by Oracle and its native-image tool allows us to generate a native executable (considerably smaller in size) out of any JVM bytecode.

This allows for an extremely fast application start-up without the need for any warm-up.

Of course, there is always a catch: Compared to a server program running on a Hotspot JVM the peak performance is lower since no JIT (just-in-time) compiler is used and therefore no runtime optimizations are possible.

(One can get close to JIT performance by using the GraalVM Enterprise edition, which is not available for free)

Setup

We want to create a typical server application that persists data in an H2 in-memory database and exposes a simple REST API for interaction.

As the title suggests we are using Spring Boot (2.7) with Kotlin (1.7) utilizing Spring WebFlux. For database access, we will use R2DBC which enables async database operations. In general, I tried to use as many Kotlin-specific features as possible to test their native support.

Code

I will not explain the in-and-out of setting up a base Spring Boot application. A complete example project can be found on GitHub.

Spring Boot Kotlin Reactive Server Example

This repository contains a simple example Spring Boot Webserver using reactive API's with Kotlin Coroutines.

Used technologies

  • Spring Webflux (reactive REST endpoints)
  • Spring Data R2DBC (reactive database access)
  • H2 in memory database
  • Spring Native GraalVM builds
  • Routes defined with Kotlin DSL (RouteConfig.kt)
  • KotlinX Serialization (native not yet working correctly)

Endpoints

  • GET /api/v1/news -> List all news entries in database
  • GET /api/v1/news/{id} -> Get specific news entry by id from database
  • POST /api/v1/news -> Add news entry to database

Build & Run native image

Requirements

  • GraalVM installed and set as environment variable JAVA_HOME (sdkman easiest variant)
  • native-image installed (use gu) and reachable

Steps

  • Build using ./gradlew :nativeCompile
  • Run using ./build/native/nativeCompile/spring-boot-kotlin-reactive-example

Known Issues

  • Native image is using Jackson serializer instead of KotlinX
    • Solved by using traditional Spring Controller with NewsDTO (Spring AOT then scans the class and registers it)
  • Native tests not working…

The Gradle setup uses the Spring Boot plugin and the experimental Spring AOT plugin which internally includes all things needed for Spring Native.

The following shows the simplified server setup. It includes a coroutine CRUD repository and initial data creator. The service exposes a simple GET REST endpoint at the root path. For this, we are using the Spring WebFlux coroutine router DSL (domain-specific language).

To keep the example short the News data class is used for persistence with Spring Data and as the return type for the REST API utilizing Kotlin Serialization (@Serializable).

Native Image

Once we have set up the service we can run it as a native image via ./gradlew nativeRun . For this to work you have to set up the following:

  • GraalVM installed and environment variable JAVA_HOME set to it (I recommend using sdkman)
  • native-image installed and reachable

The Spring AOT plugin will automatically be run in the build pipeline to create a special Spring Boot JAR which is needed to run when compiled to a native image. It will try to create all needed reachability configurations for your program to work correctly as a native image.

Problem 1

When we launch the application it will crash during start-up with the following message:

Native reflection configuration for kotlin.internal.jdk8.JDK8PlatformImplementations.() is missing.

This happens because of the DataCreator defined in the code. If you look closely you see that it is launching a coroutine to fill the H2 database with some sample data. If we drop the usage of the coroutine the application will start just fine.

This is because Kotlin coroutines are using reflection internally which is not supported by Spring AOT. We can also not define it via one of Spring AOT configuration annotations because the class named in the error message is an internal one. This is a known problem not unique to Spring but all Kotlin programs. There is an open bug ticket here.

To fix this problem we have to define a reflection-config.json file by hand. The following code has to be placed here /src/main/resources/META-INF/native-image/reflect-config.json to be picked up automatically by native-image tool.

If we now try to run the program as a native image it should start correctly.

Problem 2

When the application is launched it seems to be working fine, however, as soon as we call the endpoint we get the following error:

kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public constructor News(…

This shows us that the News data class was not properly configured by Spring AOT and no reflection configuration was created.

The main reason it was not picked up automatically is that we use the WebFlux co-router DSL for defining the REST endpoints. If we would use the traditional way (@RestControllerand @GetMapping) Spring AOT would have found it.

To solve this problem we can utilize the special annotations of the Spring Native project, which lets us define reflective configurations in code:

With the type hint manually added we are now able to call the endpoint.

Side note: If we would define the ServerResponse creation in a separate service class instead of directly under the Router DSL Spring would not be able to register the return type correctly and use Jackson Serialization in the native image instead of KotlinX Serialization. The only possible workaround is to switch to the traditional @RestController definition of the endpoints.

Start-up Time

As the most interesting part of GraalVM native-image is the start-up time here are some simple test numbers on my Mac M1 taken from the Spring Boot logs:

  • JAR: 1.695 seconds (JVM running for 2.042)
  • Native image: 0.076 seconds (JVM running for 0.078)

Conclusion

As one can probably figure out Kotlin in combination with Spring Native and GraalVM native image is not yet ready for production usage. However, it is already possible to create a simple running server that is working as intended (after some fixes are applied).

With the approach shown in this article test coverage of 100% would be needed (utilizing native tests) to be sure that everything is working as expected (making sure no reflection configuration is missing).

I am looking forward to Spring Native improving its support in the future especially as it will be a part of Spring Boot 3. The Kotlin team is also continuously working on getting the Kotlin ecosystem ready for usage with native-image. Exciting times are ahead of us we just have to wait a little bit more.

Top comments (0)