Enterprise Kotlin - Kotlin and Jakarta EE
Note: this blog post is also published on the Computas blog.
The Jakarta EE logo, by the Eclipse Foundation
If you look at the documentation on the Kotlin web page ( Kotlin for server side), you’ll find a list of available frameworks for running Kotlin server-side, a list currently consisting of Spring, Vert.x, Ktor, kotlinx.html, Micronaut, http4k and Javalin.
None of these support Jakarta EE, although for instance Spring is based on several Jakarta EE specifications, but it’s far from a full implementation.
This raises two questions to me: 1. can you use Jakarta EE and Kotlin in combination, and 2. should you?
The Kotlin docs state that “Kotlin is a great fit for developing server-side applications. It allows you to write concise and expressive code while maintaining full compatibility with existing Java-based technology stacks” ( Kotlin for server side), so the answer to whether it’s possible clearly is yes. That’s with a technocratic perspective to the concept of possible, so the question should be rephrased into is it possible within sensible time, space and hassle?
We’ll answer that by starting to walk the path there and see what happens. The first step down that road is picking a runtime that supports Jakarta EE, and getting that one up and running.
That gives us quite some options, including RedHat WildFly, Payara Server, Apache TomEE and Eclipse GlassFish. Let’s choose OpenLiberty from IBM for now, as that’s a runtime currently supporting the newest Jakarta EE, 10.
When getting started with OpenLiberty, the obvious place to start is at the Get started section of the OpenLiberty web page. From here, we can download the necessary build and configuration files that give us a starting point, be it with Maven or Gradle.
Once we’ve got OpenLiberty started up and running, it’ll be running Java code. Nice, but we’re interested in Kotlin, so the next step is to transition over to running Kotlin code. Changing the code is fast, you can even use IntelliJ to automatically convert the demo code into Kotlin. However, we’ll also need a dependency to the Kotlin compiler, so that you can use the Kotlin language, and your code can actually compile:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
In addition, you’ll need the kotlin-maven-plugin for your build to be able to parse the Kotlin classes. Towards the end of this article, you’ll find the complete config required to run your Jakarta EE application.
That’ll make your code compile, which certainly is a good start, but you will inevitably run into issues when starting your application.
Why is that? In Kotlin, all classes and methods are by default final. Thus, they cannot be extended or overridden by subclasses, or by any kind of proxy. In the CDI specification, though, it’s explicitly stated that beans must be eligible for overriding. As CDI is in the core of Jakarta EE, and used by many of the other specifications as well, this clearly has the potential of being a dealbreaker.
However, there are two solutions to this problem:
- open is a keyword in Kotlin, making the annotated class or method non-final. This solution is problematic if you’re using constructor injection though, as the generated get methods will be final.
- The all-open plugin which you can include in your pom.xml or build.gradle. This plugin will ensure that all classes annotated with one of the annotations you specify will be made non-final. You can then make for instance all @ApplicationScoped or @RequestScoped beans non-final, while sticking to the default Kotlin behaviour for your other classes. Note that you’ll have to specify the full namespace of the annotations, so make sure to use the Jakarta namespace if you’re on Jakarta EE 9 or newer, or javax if you’re still on Jakarta EE 8.
The next issue you’ll run into is that you’ll have to create no-args constructors for your beans as well. You can of course do that by hand, but it quickly becomes cumbersome. You’ll also end up with lots of wiring code that CDI should be taking care of without your intervention. The same goes when using Jakarta Persistence, which requires entities to have a no-args constructor. Luckily, there’s a compiler plugin at your service also for these issues — the no-arg plugin. If you’re using JPA, there’s a specialized version of it you should use instead — simply named jpa.
With these problems solved, you will now be able to run Jakarta EE for your Kotlin code. On my GitHub, you can find a code example and see how I’ve done it, where you through the commits can follow along.
However, if you run into some issues, the existing documentation is scarce. Here are the (few!) relevant blog posts and articles I’ve found useful while doing my research:
GitHub — gcharters/Kotlin-REST-Service: How to write a simple Kotlin REST service on Open Liberty
Guide to writing Java EE applications with Kotlin — rieckpil
Java EE Application with Kotlin | Baeldung on Kotlin
Note that several of these links talk about Java EE, even though they are published as recently as this year, while Java EE became Jakarta EE in 2019.
Now you’re ready to unleash the productivity boost from combining Jakarta EE with Kotlin. Enjoy, and happy coding!
As promised earlier on, here’s the full config you will need:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<source>src/main/kotlin</source>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<configuration>
<javaParameters>true</javaParameters>
<jvmTarget>17</jvmTarget>
<compilerPlugins>
<plugin>all-open</plugin>
<plugin>jpa</plugin>
</compilerPlugins>
<pluginOptions>
<option>all-open:annotation=jakarta.ws.rs.Path</option>
<option>all-open:annotation=jakarta.enterprise.context.ApplicationScoped</option>
<option>all-open:annotation=jakarta.persistence.Entity</option>
<option>all-open:annotation=jakarta.ws.rs.ApplicationPath</option>
<option>jpa:annotation=jakarta.ws.rs.Path</option>
<option>jpa:annotation=jakarta.enterprise.context.ApplicationScoped</option>
</pluginOptions>
</configuration>
</plugin>