Kotlin integration

Coroutines support for annotated services

You can implement annotated services with Kotlin coroutines by adding the following dependency:

build.gradle
dependencies {
    implementation platform('com.linecorp.armeria:armeria-bom:1.28.3')

    ...
    implementation 'com.linecorp.armeria:armeria-kotlin'
}

Then, define the suspending function:

class MyAnnotatedService {
    @Get("/users/{name}")
    suspend fun getUserToken(@Param("name") name: String): String {
        val user = myReactiveRepository.findByName(name).await()
        return user.token
    }
}

Note that you can omit the value of @Param if you compiled your Kotlin code with java-parameters option.

class MyAnnotatedService {
    @Get("/users/{name}")
    suspend fun getUserToken(@Param name: String): String { ... }
}

Data classes are also supported for JSON requests and responses:

data class User(val name: String, val token: String)
data class Result(val status: Int, val message: String)

class MyRestService {

    @ConsumesJson
    @ProducesJson
    @Post("/users")
    suspend fun createUser(user: User): Result {
        return myReactiveRepository.save(user).await()
    }
}

Coroutine context and dispatcher

ServiceRequestContext is injected to the default CoroutineContext of a suspend function, so you can access ServiceRequestContext from a suspend function with current()/ and the context is inherits to child contexts.

class MyContextAwareService {
    suspend fun contextPropagation(@Param name: String): String {
        // Make sure that current thread is request context aware
        assert(ServiceRequestContext.current() != null)

        return withContext(myDispatcher) {
            // Make sure that the ServiceRequestContext is propagated to other CoroutineContext which is needed
            // for logging, tracing.
            assert(ServiceRequestContext.current() != null)
            ...
        }
    }
}

Coroutine dispatcher

By default, Armeria uses a context-aware event loop as a coroutine dispatcher to run suspend functions. A context-aware blocking executor is used as a dispatcher if you annotate a service method with @Blocking or enable AnnotatedServiceBindingBuilder.useBlockingTaskExecutor().

serverBuilder
    .annotatedService()
    // Enable `useBlockingTaskExecutor`.
    .useBlockingTaskExecutor(true)
    .build(MyAnnotatedService())

// or use @Blocking annotation:

import com.linecorp.armeria.server.annotation.Blocking

class MyAnnotatedService {
    @Blocking
    @Get("/blocking/users/{name}")
    suspend fun getUserTokenBlocking(@Param("name") name: String): String {
        val id = myReactiveClient.getId(name).await()
        val user = myBlockingRepository.findById(id)
        return user.token
    }
}

Customizing CoroutineContext for annotated services

Armeria provides a way to configure a coroutine context of annotated service methods using CoroutineContextService decorator.

import com.linecorp.armeria.server.kotlin.CoroutineContextService

serverBuilder
    .annotatedService()
    .decorator(CoroutineContextService.newDecorator { ctx ->
        CoroutineName(ctx.config().defaultServiceNaming().serviceName(ctx) ?: "name")
    })
    .build(MyAnnotatedService())

Coroutines support for RestClient

Armeria provides extension methods to convert RestClientPreparation.execute() to a suspend function.

val client = RestClient.of("https://armeria.dev")
// In a coroutine scope.
val response: ResponseEntity<RestResponse> =
   client.get("/api/v1/users/{id}")
         .pathParam("id", "Armeria")
         .header(HttpHeaderNames.AUTHORIZATION, "...")
         // The suspend function executes the request and
         // returns the response.
         .execute<RestResponse>()

Coroutines support for gRPC-Kotlin

To use gRPC-Kotlin with Armeria, add the following dependency:

build.gradle
dependencies {
    implementation platform('com.linecorp.armeria:armeria-bom:1.28.3')

    ...
    implementation 'com.linecorp.armeria:armeria-grpc-kotlin'
}

Running a gRPC-Kotlin service

gRPC-kotlin support is enabled automatically by registering coroutine stub services in the same way as using gRPC-Java.

import com.linecorp.armeria.server.Server
import com.linecorp.armeria.server.docs.DocService
import com.linecorp.armeria.server.grpc.GrpcService

class YourServiceImpl : YourServiceGrpcKt.YourServiceCoroutineImplBase() {
  ...
}

// Creates GrpcService with your gRPC stub generated by gRPC-Kotlin.
val grpcService =
  GrpcService
    .builder()
    // Add your gRPC-Kotlin stub to GrpcService
    .addService(YourServiceImpl())
    .enableUnframedRequests(true)
    .build()

// Creates Armeria Server for gRPC-Kotlin stub.
Server
  .builder()
  .http(httpPort)
  .https(httpsPort)
  .service(grpcService)
  // Add DocService for browsing the list of gRPC services and
  // invoking a service operation from a web form.
  // See https://armeria.dev/docs/server-docservice for more information.
  .serviceUnder("/docs", DocService())
  .build()

Calling a gRPC-Kotlin service

You can use GrpcClients to specify a coroutine stub that ends with CoroutineStub when creating a gRPC-Kotlin client.

val client =
  GrpcClients
    .builder("https://grpc.service.com")
    .intercept(yourInterceptors)
    .build(YourServiceGrpcKt.YourServiceCoroutineStub::class.java)

Customizing CoroutineContext for gRPC-Kotlin services

When you use gRPC-Kotlin's suspend functions, the default CoroutineContext is the combination of ServiceRequestContext and an event loop CoroutineDispatcher. You can access ServiceRequestContext from the suspend functions with ServiceRequestContext.current(), similar to how you access it in annotated services.

With Armeria's integration with gRPC-Kotlin, it's easy to set up a custom CoroutineContext for your gRPC-Kotlin service. You can create your own CoroutineContext using CoroutineContextProvider, and add it to your service through Java SPI.

Note that the ServiceRequestContext is still a part of the CoroutineContext, but the event loop CoroutineDispatcher is disabled if the custom CoroutineContext is used.

class CustomCoroutineContextProvider : CoroutineContextProvider {
    override fun provide(ctx: ServiceRequestContext): CoroutineContext {
      return ... // A custom CoroutineContext
    }
}