The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Kotlinの使用

Kotlin は、JVMをターゲットにした(他の環境を含めても)非常に人気のあるプログラミング言語です。Kotlinはここ数年で人気が急上昇し、Javaを除いて最も人気のあるJVM言語となっています。

このガイドで説明するように、QuarkusはKotlinを使用するためのファーストクラスのサポートを提供しています。

この技術は、previewと考えられています。

preview では、下位互換性やエコシステムでの存在は保証されていません。具体的な改善には設定や API の変更が必要になるかもしれませんが、 stable になるための計画は現在進行中です。フィードバックは メーリングリストGitHub の課題管理 で受け付けています。

For a full list of possible statuses, check our FAQ entry.

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

If building with Mandrel, make sure to use version Mandrel 22.1 or above, for example ubi-quarkus-mandrel:22.1-java17. With older versions, you might encounter errors when trying to deserialize JSON documents that have null or missing fields, similar to the errors mentioned in the Kotlin と Jackson section.

注:Gradleプロジェクトのセットアップについては、以下を参照してください。また、詳細については、 Gradleセットアップページのガイドを参照してください。

Mavenプロジェクトの作成

まず、新しいKotlinプロジェクトが必要です。これは以下のコマンドで行うことができます。

CLI
quarkus create app org.acme:rest-kotlin-quickstart \
    --extension=kotlin,resteasy-reactive-jackson
cd rest-kotlin-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-kotlin-quickstart \
    -Dextensions="kotlin,resteasy-reactive-jackson"
cd rest-kotlin-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

When adding kotlin to the extensions list, the Maven plugin will generate a project that is properly configured to work with Kotlin. Furthermore, the org.acme.ReactiveGreetingResource class is implemented as Kotlin source code (as is the case with the generated tests). The addition of resteasy-reactive-jackson in the extension list results in importing the RESTEasy Reactive and Jackson extensions.

ReactiveGreetingResource looks like this:

ReactiveGreetingResource.kt
package org.acme

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType

@Path("/hello")
class ReactiveGreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    fun hello() = "Hello from RESTEasy Reactive"
}

コードの更新

In order to show a more practical example of Kotlin usage we will add a simple data class called Greeting like so:

Greeting.kt
package org.acme.rest

data class Greeting(val message: String = "")

We also update the ReactiveGreetingResource class like so:

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.core.MediaType

@Path("/hello")
class GreetingResource {

    @GET
    fun hello() = Greeting("hello")
}

これらの変更により、 /hello エンドポイントは、単純な文字列ではなくJSONオブジェクトで応答するようになりました。

To make the test pass, we also need to update ReactiveGreetingResourceTest like so:

import org.hamcrest.Matchers.equalTo

@QuarkusTest
class ReactiveGreetingResourceTest {

    @Test
    fun testHelloEndpoint() {
        given()
          .`when`().get("/hello")
          .then()
             .statusCode(200)
             .body("message", equalTo("hello"))
    }

}

Kotlin version

The Quarkus Kotlin extension already declares a dependency on some base Kotlin libraries like kotlin-stdlib-jdk8 and kotlin-reflect. The Kotlin version of these dependencies is declared in the Quarkus BOM and is currently at {kotlin-version}. It is therefore recommended to use the same Kotlin version for other Kotlin libraries. When adding a dependency to another base Kotlin library (e.g. kotlin-test-junit5) you don’t need to specify the version, since the Quarkus BOM includes the Kotlin BOM.

This being said, you still need to specify the version of the Kotlin compiler to use. Again, it is recommended to use the same version which Quarkus uses for the Kotlin libraries.

Using a different Kotlin version in a Quarkus application is typically not recommended. But in order to do so, you must import the Kotlin BOM before the Quarkus BOM.

重要なMavenの設定ポイント

生成された pom.xml には、Kotlin が選択されていない場合と比較して、以下の修正が含まれています。

  • quarkus-kotlin アーティファクトが依存関係に追加されています。このアーティファクトはライブリロードモードでの Kotlin のサポートを提供します (これについては後ほど説明します)。

  • kotlin-stdlib-jdk8 も依存関係として追加されています。

  • Maven の sourceDirectorytestSourceDirectory ビルドプロパティーは、Kotlin ソースを指すように設定されています (それぞれ src/main/kotlinsrc/test/kotlin )。

  • kotlin-maven-plugin は以下のように設定されています。

pom.xml
<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> (1)
        </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>
1 Enables the all-open annotation plugin (see discussion below)

注意すべき重要なこととして、 all-open Kotlinコンパイラーのプラグインを使用していることです。なぜこのプラグインが必要なのかを理解するためには、まず、Kotlin コンパイラから生成されるクラスはデフォルトで final でマークされていることに注目してください。

しかし、 final クラスであることは、 ダイナミックプロキシー を作成する必要がある様々なフレームワークではうまく機能しません。

このように、 all-open Kotlin コンパイラプラグインを使うと、特定のアノテーションを持つクラスを final としてマークし ない ようにコンパイラを設定することができます。上のスニペットでは、 javax.ws.rs.Path でアノテーションされたクラスを final ではないようにします。

If your application contains Kotlin classes annotated with javax.enterprise.context.ApplicationScoped for example, then <option>all-open:annotation=javax.enterprise.context.ApplicationScoped</option> needs to be added as well. Same goes for any class that needs to have a dynamic proxy created at runtime.

Quarkusの将来のバージョンでは、この設定を変更する必要がないようにKotlinコンパイラプラグインを設定するようになる予定です。

重要なGradle設定ポイント

Mavenの設定と同様、Gradleを使用する場合、Kotlinを選択すると以下のような修正が必要になります。

  • quarkus-kotlin アーティファクトが依存関係に追加されています。このアーティファクトはライブリロードモードでの Kotlin のサポートを提供します (これについては後ほど説明します)。

  • kotlin-stdlib-jdk8 も依存関係として追加されています。

  • Kotlin プラグインが有効になり、暗黙のうちに sourceDirectorytestSourceDirectory のビルドプロパティーが追加され、Kotlin ソース (それぞれ src/main/kotlinsrc/test/kotlin ) を指すようになります。

  • all-open Kotlinプラグインは、アノテーションがハイライトされているクラスを最終的なものとしてマークしないようにコンパイラに指示します (必要に応じてカスタマイズしてください)

  • ネイティブイメージを使用する場合は、http (または https) プロトコルの使用を宣言しなければなりません。

  • 以下に設定例を示します。

plugins {
    id 'java'
    id 'io.quarkus'

    id "org.jetbrains.kotlin.jvm" version "{kotlin-version}" (1)
    id "org.jetbrains.kotlin.plugin.allopen" version "{kotlin-version}" (1)
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:{kotlin-version}'

   implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")

    implementation 'io.quarkus:quarkus-resteasy-reactive'
    implementation 'io.quarkus:quarkus-resteasy-reactive-jackson'
    implementation 'io.quarkus:quarkus-kotlin'

    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'
}

group = '...' // set your group
version = '1.0.0-SNAPSHOT'

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

allOpen { (2)
    annotation("javax.ws.rs.Path")
    annotation("javax.enterprise.context.ApplicationScoped")
    annotation("io.quarkus.test.junit.QuarkusTest")
}

compileKotlin {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_11
    kotlinOptions.javaParameters = true
}

compileTestKotlin {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_11
}
1 Kotlinプラグインのバージョンを指定する必要があります。
2 上記のMavenガイドにあるように、all-open設定が必要です。

または、GradleのKotlin DSLを使用している場合:

plugins {
    kotlin("jvm") version "{kotlin-version}" (1)
    kotlin("plugin.allopen") version "{kotlin-version}"
    id("io.quarkus")
}

repositories {
    mavenLocal()
    mavenCentral()
}

val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project

group = "..."
version = "1.0.0-SNAPSHOT"


repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))

    implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))

    implementation("io.quarkus:quarkus-kotlin")
    implementation("io.quarkus:quarkus-resteasy-reactive")
    implementation("io.quarkus:quarkus-resteasy-reactive-jackson")

    testImplementation("io.quarkus:quarkus-junit5")
    testImplementation("io.rest-assured:rest-assured")
}

group = '...' // set your group
version = "1.0.0-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

allOpen { (2)
    annotation("javax.ws.rs.Path")
    annotation("javax.enterprise.context.ApplicationScoped")
    annotation("io.quarkus.test.junit.QuarkusTest")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
    kotlinOptions.javaParameters = true
}
1 Kotlinプラグインのバージョンを指定する必要があります。
2 上記のMavenガイドにあるように、all-open設定が必要です。

Overriding the Quarkus BOM Kotlin version (Gradle)

If you want to use a different version than the one specified by Quarkus' BOM in your application (for example, to try pre-release features or for compatibility reasons), you can do so by using the strictly {} version modifier in your Gradle dependencies. For instance:

plugins {
    id("io.quarkus")
    kotlin("jvm") version "1.7.0-Beta"
    kotlin("plugin.allopen") version "1.7.0-Beta"
}

configurations.all {
    resolutionStrategy {
        force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0-Beta"
        force "org.jetbrains.kotlin:kotlin-reflect:1.7.0-Beta"
    }
}

ライブ・リロード

Quarkusは、ソースコードに加えられた変更をライブでリロードする機能をサポートしています。このサポートはKotlinでも利用できます。つまり、開発者はKotlinのソースコードを更新して、変更が反映されたことをすぐに確認することができます。

この機能の動作を確認するには、まず次のコマンドを実行します。

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

http://localhost:8080/hello に対してHTTP GETリクエストを実行すると、 message フィールドに hello という値を持つJSONメッセージが表示されます。

ここで、お好きなエディタやIDEを使って、 ReactiveGreetingResource.kt をアップデートし、 hello のメソッドを以下のように変更します。

fun hello() = Greeting("hi")

http://localhost:8080/hello に対してHTTP GETリクエストを実行すると、 message フィールドに hi という値を持つJSONメッセージが表示されるはずです。

注意点としては、お互いに依存関係にあるJavaとKotlinの両方のソースに変更を加えた場合、ライブリロード機能が利用できないということです。将来的にはこの制限を緩和したいと考えています。

Configuring live reload compiler

If you need to customize the compiler flags used by kotlinc in development mode, you can configure them in the quarkus plugin:

Maven
<plugin>
  <groupId>${quarkus.platform.group-id}</groupId>
  <artifactId>quarkus-maven-plugin</artifactId>
  <version>${quarkus.platform.version}</version>

  <configuration>
    <source>${maven.compiler.source}</source>
    <target>${maven.compiler.target}</target>
    <compilerOptions>
      <compiler>
        <name>kotlin</name>
        <args>
          <arg>-Werror</arg>
        </args>
      </compiler>
    </compilerOptions>
  </configuration>

  ...
</plugin>
Gradle (Groovy DSL)
quarkusDev {
    compilerOptions {
        compiler("kotlin").args(['-Werror'])
    }
}
Gradle (Kotlin DSL)
tasks.quarkusDev {
     compilerOptions {
        compiler("kotlin").args(["-Werror"])
    }
}

アプリケーションのパッケージング

いつものように、アプリケーションは以下の方法でパッケージ化されます。

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

そして次のコマンドで実行できます。 java -jar target/quarkus-app/quarkus-run.jar.

ネイティブ実行可能ファイルを次のようにビルドすることもできます。

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

Kotlin と Jackson

com.fasterxml.jackson.module:jackson-module-kotlin の依存関係と quarkus-jackson エクステンション(または quarkus-resteasy-jacksonquarkus-resteasy-reactive-jackson エクステンションのいずれか)がプロジェクトに追加されている場合、Quarkus は KotlinModule から ObjectMapper の Bean を自動的に登録します(詳細は このガイドを参照してください)。

Kotlin のデータクラスを native-image で使用すると、Kotlin Jackson Module が登録されているにもかかわらず、 JVM バージョンでは発生しないシリアライズエラーが発生することがあります。これは、より複雑なJSON階層を持っている場合に特に見られる現象で、下位のノードで問題が発生するとシリアライズに失敗します。表示されるエラーメッセージはキャッチオールで、通常はルートオブジェクトの問題を表示しますが、必ずしもそうではない場合もあります。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Address` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

To ensure full-compatibility with native-image, it is recommended to apply the Jackson @field:JsonProperty("fieldName") annotation, and set a nullable default, as illustrated below. You can automate the generation of Kotlin data classes for your sample JSON using IntelliJ IDEA plugins (such as JSON to Kotlin Class), and easily enable the Jackson annotation and select nullable parameters as part of the auto-code generation.

import com.fasterxml.jackson.annotation.JsonProperty

data class Response(
	@field:JsonProperty("chart")
	val chart: ChartData? = null
)

data class ChartData(
	@field:JsonProperty("result")
	val result: List<ResultItem?>? = null,

	@field:JsonProperty("error")
	val error: Any? = null
)

data class ResultItem(
	@field:JsonProperty("meta")
	val meta: Meta? = null,

	@field:JsonProperty("indicators")
	val indicators: IndicatorItems? = null,

	@field:JsonProperty("timestamp")
	val timestamp: List<Int?>? = null
)

...

KotlinとKubernetesクライアント

quarkus-kubernetes エクステンションを使用して、Kotlin クラスを CustomResource 定義にバインドする場合(オペレーターを構築する場合など)、基盤となる Fabric8 Kubernetes Client が独自の静的な Jackson ObjectMapper を使用することに注意する必要があります。これは KotlinModule を使用して以下のように設定できます。

import io.fabric8.kubernetes.client.utils.Serialization
import com.fasterxml.jackson.module.kotlin.KotlinModule

...

Serialization.jsonMapper().registerModule(KotlinModule())
Serialization.yamlMapper().registerModule(KotlinModule())

ネイティブイメージへのコンパイル時には慎重にテストし、問題が発生した場合はJava互換のJacksonバインディングにフォールバックしてください。

Coroutines support

エクステンション

The following extensions provide support for Kotlin Coroutines by allowing the use of Kotlin’s suspend keyword on method signatures.

Extension Comments

quarkus-resteasy-reactive

Support is provided for JAX-RS Resource Methods

quarkus-rest-client-reactive

Support is provided for REST Client interface methods

quarkus-smallrye-reactive-messaging

Support is provided for Reactive messaging methods

quarkus-scheduler

Support is provided for scheduler methods

quarkus-smallrye-fault-tolerance

Support is provided for the declarative annotation-based API

Kotlin coroutines と Mutiny

Kotlin coroutines provide an imperative programming model that actually gets executed in an asynchronous, reactive manner. To simplify the interoperability between Mutiny and Kotlin there is the module io.smallrye.reactive:mutiny-kotlin, described here.

Kotlinでの CDI @Inject

Kotlinのリフレクションアノテーション処理はJavaとは異なります。CDIの@Injectを使用していると、以下のようなエラーが発生することがあります。"kotlin.UninitializedPropertyAccessException: lateinitプロパティーxxxが初期化されていません"

以下の例では、アノテーションに @field を追加することで、Kotlin のリフレクションアノテーション定義に @Target がない場合に対応し、問題を簡単に解決できます。

import javax.inject.Inject
import javax.enterprise.inject.Default
import javax.enterprise.context.ApplicationScoped

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType



@ApplicationScoped
class GreetingService {

    fun greeting(name: String): String {
        return "hello $name"
    }

}

@Path("/")
class ReactiveGreetingResource {

    @Inject
    @field: Default (1)
    lateinit var service: GreetingService

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/hello/{name}")
    fun greeting(name: String): String {
        return service.greeting(name)
    }

}
1 Kotlinでは、アノテーション定義に@Targetがないため、@field: xxx修飾子が必要です。この例では @field: xxx を追加します。@Defaultが修飾子として使用され、デフォルトBeanの使用を明示的に指定します。

あるいは、Javaの例を変更せずに動作し、テスト性が高く、Kotlinのプログラミングスタイルに最も適しているコンストラクタ・インジェクションの使用をお勧めします。

import javax.enterprise.context.ApplicationScoped

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType

@ApplicationScoped
class GreetingService {
    fun greeting(name: String): String {
        return "hello $name"
    }
}

@Path("/")
class ReactiveGreetingResource(
    private val service: GreetingService
) {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/hello/{name}")
    fun greeting(name: String): String {
        return service.greeting(name)
    }

}