1. Introduction

Quarkus is a Java framework designed for cloud-native applications. It’s tailored for use with Kubernetes and promises to deliver small artifacts, extremely fast boot time, and reduced time-to-first-request. At the same time, it’s built on top of existing Java standards, meaning we can leverage what we already know from other frameworks.

In this tutorial, we’re going to have a look at using Quarkus with Kotlin. We’ll see what it is, what we can do with it, and how to use it.

2. Setting Up

Before using Quarkus, we need to include the latest version in our build, which is 3.15.1 at the time of writing.

The recommended way to start a new Quarkus project is to use the provided CLI. After installing this, we can create a new Quarkus project:

$ quarkus create app com.baeldung:quarkus-kotlin --extension='kotlin,rest-jackson'

By default, this creates a new Maven project in Kotlin, with the Quarkus rest-jackson extension added in order to support JSON APIs.

If we wanted to use Gradle then we can add the –gradle flag, or the –gradle-kotlin-dsl flag if we want to use Gradle with the Kotlin DSL.

Alternatively, we can create a new project directly from Maven using an archetype:

$ mvn io.quarkus.platform:quarkus-maven-plugin:3.14.4:create \
    -DprojectGroupId=com.baeldung \
    -DprojectArtifactId=quarkus-kotlin \
    -Dextensions='kotlin,rest-jackson'

The result of this is exactly the same as using the Quarkus CLI.

The resulting project includes the Quarkus BOM. This helps manage version numbers for several libraries known to work with Quarkus, including the supported versions of Kotlin language libraries.

3. Building and Running

Once we’ve got our project, we need to be able to build and run our application.

Quarkus comes with a Maven plugin that allows us to run a development server directly from our build:

$ mvn quarkus:dev
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2024-09-20 07:55:47,144 INFO  [io.quarkus] (Quarkus Main Thread) quarkus-kotlin 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.14.4) started in 1.302s. Listening on: http://localhost:8080

2024-09-20 07:55:47,146 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2024-09-20 07:55:47,146 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, kotlin, rest, rest-jackson, smallrye-context-propagation, vertx]

--
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

Doing this builds our application, starts it running, and gives access to some powerful development tools. As part of this, Quarkus automatically configures the Kotlin compiler for hot reload, and running in development mode automatically leverages this. This means that we can make changes to our application and they’ll be automatically compiled and included in the running application.

When we’re ready, we can build the final application. If we run a simple mvn install then we’ll get a Java application that’s ready to run:

$ mvn install
.....
$ java -jar target/quarkus-app/quarkus-run.jar
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2024-09-20 07:55:24,542 INFO  [io.quarkus] (main) quarkus-kotlin 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.14.4) started in 0.488s. Listening on: http://0.0.0.0:8080
2024-09-20 07:55:24,547 INFO  [io.quarkus] (main) Profile prod activated.
2024-09-20 07:55:24,547 INFO  [io.quarkus] (main) Installed features: [cdi, kotlin, rest, rest-jackson, smallrye-context-propagation, vertx]

If we have GraalVM installed then we can also build an entirely native application:

$ mvn install -Dnative
.....
$ ./target/quarkus-kotlin-1.0.0-SNAPSHOT-runner
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2024-09-20 08:08:41,456 INFO  [io.quarkus] (main) quarkus-kotlin 1.0.0-SNAPSHOT native (powered by Quarkus 3.14.4) started in 0.135s. Listening on: http://0.0.0.0:8080
2024-09-20 08:08:41,459 INFO  [io.quarkus] (main) Profile prod activated.
2024-09-20 08:08:41,459 INFO  [io.quarkus] (main) Installed features: [cdi, kotlin, rest, rest-jackson, smallrye-context-propagation, vertx]
2024-09-20 08:08:43,342 INFO  [io.quarkus] (Shutdown thread) quarkus-kotlin stopped in 0.005s

3.1. Building Docker Images

In addition to building a Java or native application to run, we can also build a Docker container that can run in any Docker system.

In order to do this, we need to add an additional Quarkus extension to our application. Exactly which one depends on the way that we want to build our container. The recommended extension to start with is container-image-jib which uses Jib to build our container image. However, there may be issues using this if we’re not running the build on a Linux machine, and the container-image-docker extension might be more reliable in those cases.

If we have the Quarkus CLI available, then we can add this extension:

$ quarkus extension add container-image-jib
[SUCCESS] ✅  Extension io.quarkus:quarkus-container-image-jib has been installed

Alternatively, we can add the dependency directly to our pom.xml file:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-jib</artifactId>
</dependency>

Once we’ve done this, we can build our application:

$ mvn package -Dnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true

The quarkus.container-image.build property causes Quarkus to build our container image as part of the build process. The quarkus.native.container.build allows us to build a native Linux container without GraalVM installed, or if we’re not running the build process on a Linux machine.

The end of the output tells us the name of the Docker image that was created. We can immediately run this locally:

$ docker run baeldung/quarkus-kotlin:1.0.0-SNAPSHOT
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2024-10-07 07:25:18,935 INFO  [io.quarkus] (main) quarkus-kotlin 1.0.0-SNAPSHOT native (powered by Quarkus 3.14.4) started in 0.044s. Listening on: http://0.0.0.0:8080
2024-10-07 07:25:18,935 INFO  [io.quarkus] (main) Profile prod activated.
2024-10-07 07:25:18,935 INFO  [io.quarkus] (main) Installed features: [cdi, kotlin, rest, rest-jackson, smallrye-context-propagation, vertx]

4. Exposing REST Resources

Now that we have a working application, we’re ready to start exposing services for our clients to access. Quarkus is an implementation of the JAX-RS specification for writing RESTful services. This means that we’ll be using the terminology defined by JAX-RS and all of our imports come from the jakarta.ws.rs package.

Our  services are all exposed by writing JAX-RS resource classes. These are simply classes that have the appropriate JAX-RS annotations on them. Specifically, they need to have the @Path annotation on the class itself to indicate the base path under which the resource is accessed. We then need to annotate the appropriate methods with annotations such as @GET, @POST, etc. as well as any other relevant JAX-RS annotations:

@Path("/hello")
class GreetingResource {

    @GET
    fun hello() = "Hello, World!"
}

Simply writing a class like this is all that’s needed. Quarkus automatically discovers this and exposes it for clients to access:

GET /hello HTTP/1.1
Host: localhost:8080


HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
content-length: 13

Hello, World!

Since we added the rest-jackson extension when we created our project, we’ll also automatically get full JSON support for both request and response bodies. Quarkus automatically determines that this is needed and serializes/deserializes as appropriate:

@Path("/hello")
class GreetingResource {

    @GET
    fun hello() = Greeting("Hello, World!")
}

data class Greeting(val greeting: String)

This then automatically returns an application/json response with our object correctly formatted:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
content-length: 28

{
    "greeting": "Hello, World!"
}

5. Coroutines Support

Quarkus has explicit support for Kotlin coroutines. A number of different Quarkus extensions – including quarkus-rest – directly allow us to write suspend functions as our handlers:

@Path("/coroutine")
class CoroutineResource {
    @GET
    suspend fun get() : String {
        return doSomething()
    }
}

Being able to use suspend functions directly as our resource handlers means that we’re already in a coroutine scope, and can immediately call other suspend functions. This means we don’t need to use constructs such as runBlocking to be able to execute coroutines from our handlers.

Even though we’ve not discussed them in this article, we can also use suspend functions directly from other Quarkus extensions such as quarkus-messaging, quarkus-scheduler and more.

6. Dependency Injection

In the same way that Quarkus uses JAX-RS for HTTP resources, it uses CDI for dependency injection. Within this, we need to define two types of classes: those that we can inject and those that receive the injections.

At its simplest, we can define an injectable class by annotating it with @ApplicationScoped. This causes Quarkus to create a single instance of this class and share it throughout the entire application:

@ApplicationScoped
class InjectableService {
    fun greeting() = "Hello, World!"
}

We can then make use of these classes by having Quarkus inject them into other classes using the @Inject annotation.

With this, we can support constructor injection. Within Kotlin, this means that we need to define an explicit constructor that we then annotate with our @Inject annotation:

@Path("/inject")
class InjectResource @Inject constructor(private val injectableService: InjectableService) {

    @GET
    fun handler() = injectableService.greeting()
}

Alternatively, we can use field injection. This requires us to define the field using late initialization, and annotate it with the @Inject annotation:

@Path("/inject")
class InjectResource {
    @Inject
    lateinit var injectableService: InjectableService

    @GET
    fun handler() = injectableService.greeting()
}

In either of these cases, Quarkus automatically wires up our application correctly.

7. Testing

In order to know that our application works correctly, we need to be able to test it. Our Quarkus application that we’ve created comes with full support for both unit and integration testing, built on top of the JUnit Jupiter test engine and using REST-Assured for performing the actual tests.

We can write unit tests by annotating our test class with @QuarkusTest. Within the test itself, we can then use REST-Assured to actually interact with our service:

@QuarkusTest
class GreetingResourceUnitTest {
    @Test
    fun `when getting the hello resource then we got the correct response`() {
        given()
          .`when`().get("/hello")
          .then()
          .statusCode(200)
          .body("greeting", `is`("Hello, World!"))
    }
}

This test starts up our application, sends an HTTP request to the /hello resource that we saw earlier, and then asserts that the response is correct.

Integration tests look exactly the same, only we use @QuarkusIntegrationTest instead:

@QuarkusIntegrationTest
class InjectResourceIntegrationTest {
    @Test
    fun `when getting the inject resource then we got the correct response`() {
        given()
          .`when`().get("/inject")
          .then()
          .statusCode(200)
          .body(`is`("Hello, World!"))
    }
}

This causes the test to run the actual built application as a separate process alongside our tests, instead of running our application code within the test process.

8. Conclusion

This was a quick introduction to using Quarkus with Kotlin. Quarkus fully supports Kotlin and allows us to use coroutines and suspend functions natively. We’ve only discussed the basics of this library, but it’s capable of handling many more advanced scenarios.


原始标题:Quarkus and Kotlin