Spring DI & Kotlin on Quarkus

July 29, 2019

Kotlin & Spring DI outside in Quarkus

Introduction

Quarkus supersonic subatomic Java is, since its release, bringing a lot of cool features with it : fail fast thanks to build time evaluation, easy way to use microprofile specs, hot reload for backend development, etc.

Let’s find out today how easy it is to combine Kotlin & Spring DI on Quarkus in a blink of an eye.

Project Creation

Let’s first begin with the project creation using mvn

mvn io.quarkus:quarkus-maven-plugin:0.19.1:create

Answer **yes **to create a REST resource and name it with the path to it being /hello

Once this is done, we should have an empty project, containing two Docker files, a java file named HelloResource and a resource folder containing the explanation webpage and application.properties

Extensions

You can see extensions as extra dependencies similar as starters in spring-boot.

You can list the available extensions by executing

mvn quarkus:list-extensions

Add Spring-DI

To add spring-di to the project, we’ll execute the following command

mvn quarkus:add-extension -Dextensions="quarkus-spring-di"

This will add a new dependency into our pom.xml named quarkus-spring-di.

This is not enough, we also need an extra dependency from the spring framework to be able to use the annotation set of spring di.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <!--   can be any version higher than 3.2.0.RELEASE -->
    <version>5.1.8.RELEASE</version>         
</dependency>

Add & Configure Kotlin

Same method to add the Kotlin extension of Quarkus

mvn quarkus:add-extension -Dextensions="quarkus-kotlin"

This is still not enough as we need to configure Kotlin. The quarkus-kotlin will allow for hot reload, but it is not enough.

We need for our Kotlin application to be able to run to add the stdlib

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>${kotlin.version}</version>
</dependency>

And the kotlin maven plugin to be configured as the following, using the all-open plugin

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>test-compile</id>
            <goals>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <compilerPlugins>
            <plugin>all-open</plugin>
        </compilerPlugins>

        <pluginOptions>
            <!-- Each annotation is placed on its own line -->
            <option>all-open:annotation=javax.ws.rs.Path</option>
        </pluginOptions>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

The all-open plugin is needed to mark all classes as open as opposed to the way they’re managed in Kotlin as final .

Final classes can’t work with Java EE for example, or any other framework or library needing to create dynamic proxies.

This is thus the reason why this plugin is needed, to avoid having final classes by default.

Writing the App

Let’s write a very basic app, that doesn’t do much but to GET information through our REST service.

Each time the service is invoked, we’ll add an entry to a list that is then retrievable through another path of the resource.

HelloResource.kotlin

@Path("/hello")
class HelloResource {
    @Autowired
    lateinit var helloService: HelloService

    @GET
    @Path("/latest")
    @Produces(TEXT_PLAIN)
    fun hello() = helloService.getLatestEntry()

    @GET
    @Path("/all")
    @Produces(TEXT_PLAIN)
    fun all() = helloService.getAll()
}

HelloService.kotlin

@Service
class HelloService {
    @Autowired
    lateinit var helloRepository: HelloRepository

    @Value("\${environment}")
    lateinit var environment: String

    fun getLatestEntry() = "Environment = $environment; Message : ${helloRepository.getRepositoryEntry()}"

    fun getAll() = helloRepository.getAll()
}

HelloRepository.kotlin

@Repository
class HelloRepository {
    private val strings = mutableListOf<String>()
    fun getRepositoryEntry() = "Latest Entry. TS : ${now()}".also { strings.add(it) }
    fun getAll() = strings
}

application.properties

# Configuration file
# key = value
environment=development

Multiple things to notice here

  • To allow for dependency injection, we need to use the modifiers lateinit var because the beans will be created after the instantiation of the class val is not accepted for them
  • We use @Value but we need to escape the $ : this is needed because of $ being interpreted as a variable inside of Kotlin’s string templates

Testing the App

Opening the browser and testing our resource we can see the two following result possibilities

/hello/latest

Environment = development; Message : Latest Entry. TS : 2019-07-20T01:00:25.421

The environment property has been injected correctly via @Value . This also means that every bean we injected using @Autowired is correctly injected.

/hello/all

[Latest Entry. TS : 2019-07-20T01:00:03.098, Latest Entry. TS : 2019-07-20T01:00:16.773, Latest Entry. TS : 2019-07-20T01:00:25.421, Latest Entry. TS : 2019-07-20T01:06:08.857, Latest Entry. TS : 2019-07-20T01:06:09.103, Latest Entry. TS : 2019-07-20T01:06:09.235, Latest Entry. TS : 2019-07-20T01:06:09.379, Latest Entry. TS : 2019-07-20T01:06:09.513, Latest Entry. TS : 2019-07-20T01:06:09.655, Latest Entry. TS : 2019-07-20T01:06:09.799]

Conclusion

In five minutes chrono, we have been able to create the project, expose a resource, prepare multiple layers and use Spring DI to inject beans in those different layers.

All this generating a fat-jar as small as 111kband starting in a second using java -jar