Kotlin 5.1.1-SNAPSHOT
On this page

Kotlin

1 Introduction

This project provides various extensions and improvements to the Micronaut and Kotlin experience.

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Kotlin Runtime Support

The micronaut-kotlin-runtime dependency adds the following features:

  • Support for defining configuration with config4k.

  • A runtime dependency on jackson-module-kotlin

To enable the above features add the dependency to your build:

implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")

Config4k Support

3.1 Config4k

Config4k is a type safe configuration format for Kotlin based on HOCON (Human-Optimized Config Object Notation). Configuration files are defined using the conf extension. The following an example configuration file:

An Example src/main/resources/application.conf
micronaut {
  server {
    port = 8081
  }
}

test-property = "bad-value"

custom {
  user = ${USER}
}

4 Ktor Support

Ktor is a Kotlin framework for building connected applications. The micronaut-ktor module includes support for using Ktor as the server instead of Micronaut’s native HTTP server.

This allows users familiar with Ktor to use Micronaut features such as Dependency Injection, AOP, configuration management and so on.

implementation("io.micronaut.kotlin:micronaut-ktor")
Tip
See the Example application which demonstrates how to setup a Micronaut Ktor application

4.1 The KtorApplication class

The entry point for a Micronaut Ktor application is an Application class. An example class can be seen below:

Example Application class

4.2 Defining Ktor Modules

To define Ktor modules you can create classes that subclass io.micronaut.ktor.KtorApplicationBuilder to install plugins or io.micronaut.ktor.KtorRoutingBuilder to configure routes.

For example, the following installs the Jackson plugin when ktor-serialization-jackson is on the classpath:

Installing Plugins

To build application routes you can use the KtorRoutingBuilder:

Defining Routes

5 Kotlin Extension Functions

The micronaut-kotlin-extension-functions dependency adds a variety of convenience functions to make using Micronaut with Kotlin more user-friendly. For example, reified type parameters help alleviate the need for using ::class.java in many places where it would otherwise be required.

implementation("io.micronaut.kotlin:micronaut-kotlin-extension-functions")
Note
You will need to import the functions like this: import io.micronaut.kotlin.FUNCTION_NAME
Warning
Do be aware when defining extension functions in this project or your own, an extension that shadows a member function may have unexpected behavior, and does not throw a compiler error but rather warns. See this Kotlin discussion topic for the latest information.

5.1 Context Extensions

5.2 HTTP Extensions

BlockingHttpClient Extensions

Single Response Extensions

Test Demonstrating Single Response Client Extension usage
// Compare exchange usage
val exchangeOneConventional: HttpResponse<Hero> = client.exchange(HttpRequest.GET<Any>("/heroes/any"), Argument.of(Hero::class.java))
val exchangeOneReified: HttpResponse<Hero> = client.exchangeObject<Hero>(HttpRequest.GET("/heroes/any"))

// Compare retrieve usage
val retrieveOneConventional: Hero = client.retrieve(HttpRequest.GET<Any>("/heroes/any"), Argument.of(Hero::class.java))
val retrieveOneReified: Hero = client.retrieveObject<Hero>(HttpRequest.GET("/heroes/any"))

List Response Extensions

Test Demonstrating List Response Client Extension usage
// Compare exchange usage
val exchangeListConventional: HttpResponse<MutableList<Hero>> = client.exchange(HttpRequest.GET<Any>("/heroes/list"), Argument.listOf(Hero::class.java))
val exchangeListReified: HttpResponse<List<Hero>> = client.exchangeList<Hero>(HttpRequest.GET("/heroes/list"))

// Compare retrieve usage
val retrieveListConventional: MutableList<Hero> = client.retrieve(HttpRequest.GET<Any>("/heroes/list"), Argument.listOf(Hero::class.java))
val retrieveListReified: List<Hero> = client.retrieveList<Hero>(HttpRequest.GET("/heroes/list"))

HttpMessage Extensions

Test Demonstrating HttpMessage Extension usage
// Compare body member usage (Not exceptional path)
val exchangeOneConventionalBody: Hero? = exchangeOneConventional.body.getOrNull()
val exchangeOneReifiedBody: Hero? = exchangeOneReified.bodyOrNull

// Compare getBody() usage (Exceptional path)
val exchangeOneConventionalCustomException: HttpClientResponseException = assertThrows<HttpClientResponseException> {
    client.exchange(HttpRequest.GET<Any>("/heroes/conflict"), Argument.of(Hero::class.java))
}
val exchangeOneReifiedCustomException: HttpClientResponseException = assertThrows<HttpClientResponseException> {
    client.exchangeObject<Hero>(HttpRequest.GET("/heroes/conflict"))
}
val exchangeOneConventionalCustomExceptionBody: HeroJsonError? = exchangeOneConventionalCustomException.response.getBody(HeroJsonError::class.java).getOrNull()
val exchangeOneReifiedCustomExceptionBody: HeroJsonError? = exchangeOneReifiedCustomException.response.getBodyObject<HeroJsonError>()

5.3 Inject Extensions

5.4 Runtime Extensions

An Example Application using the extension function.
object Application

fun main(args: Array<String>) {
    startApplication<Application>(*args) {
        packages("org.example.app")
        mapError<RuntimeException> { 500 }
    }
}

5.5 Scheduling Extensions

5.6 JSON Extensions

JsonMapper Extensions

The JsonMapper extension functions provide convenient ways to deserialize JSON from various sources using reified type parameters, eliminating the need to explicitly pass type information.

Deserialize from JsonNode

The readValueFromTree extension allows you to deserialize a JsonNode directly to a typed value without requiring explicit type arguments:

Test Demonstrating JsonNode deserialization
// Instead of:
val value: MyData = jsonMapper.readValueFromTree(node, Argument.of(MyData::class.java))

// You can use:
val value = jsonMapper.readValueFromTree<MyData>(node)

This is particularly useful when working with the JSON tree API for partial parsing or tree manipulation operations.

Deserialize from InputStream

The readValue extension for InputStream provides a clean way to parse JSON from a stream:

Parsing JSON from an InputStream
// Instead of:
val value: MyData = jsonMapper.readValue(inputStream, Argument.of(MyData::class.java))

// You can use:
val value = jsonMapper.readValue<MyData>(inputStream)

Deserialize from ByteArray

The readValue extension for ByteArray is convenient for parsing JSON from raw bytes:

Parsing JSON from a ByteArray
// Instead of:
val value: MyData = jsonMapper.readValue(jsonBytes, Argument.of(MyData::class.java))

// You can use:
val value = jsonMapper.readValue<MyData>(jsonBytes)

Deserialize from ReadBuffer

For Micronaut’s ReadBuffer type, the extension provides seamless deserialization:

Parsing JSON from a ByteBuffer
// Instead of:
val value: MyData = jsonMapper.readValue(buffer, Argument.of(MyData::class.java))

// You can use:
val value = jsonMapper.readValue<MyData>(buffer)

This is especially useful in server implementations that work directly with buffers.

Deserialize from String

The readValue extension for String is the most convenient for handling JSON text:

Parsing JSON from a String
// Instead of:
val value: MyData = jsonMapper.readValue(jsonString, Argument.of(MyData::class.java))

// You can use:
val value = jsonMapper.readValue<MyData>(jsonString)

Null Handling

All extension functions return a nullable type (T?) to properly handle cases where deserialization results in JSON null values.

Handling null values in deserialization
val possiblyNull: MyData? = jsonMapper.readValue<MyData>(jsonString)

if (possiblyNull != null) {
    // Process the deserialized value
    println(possiblyNull.name)
} else {
    // Handle null case
    println("JSON was null")
}

6 Breaking Changes

This section documents breaking changes for Kotlin and Ktor versions:

Micronaut Kotlin 5.1.1-SNAPSHOT

Micronaut Kotlin’s Ktor module is now based on Ktor 3.x and includes breaking changes relative to the previous Ktor 2-based integration.

Key changes include Ktor 3 environment and server bootstrap redesign, including the move from ApplicationEngineEnvironmentBuilder to ApplicationEnvironmentBuilder, the introduction of ServerConfig, and the change in embeddedServer() to return EmbeddedServer.

For more information, refer to the following Ktor guide:

7 Repository

You can find the source code of this project in this repository: