Scala integration

As a matter of fact, there are three distinct ways to integrate Armeria and Scala:

  • by using the armeria-scala library;
  • by using the http4s-armeria library;
  • or just by using Armeria directly in your Scala projects. In that case, the supplied API would be fully in Java.

If you're searching for the most Scala-way experience of using Armeria, take a look at armeria-scala and http4s-armeria. These two libraries significantly differentiate from each other.

  • armeria-scala is built on top of the Scala Standard Library, and asynchronous computations are provided by the scala.concurrent library. By picking this library, you will work with HTTP through the Armeria API and get the handy functionality to bridge Java API to Scala.
  • http4s-armeria, in turn, is built on top of the Functional Programming Libraries ā€” http4s exposes typeful, functional, streaming HTTP API, and Cats-Effect accords a reach functionality for the asynchronous and concurrent computations. This means that Armeria is used as the underlying Backend for the Server and Client.

Since the http4s-armeria is a third-party project, its documentation is not listed here, so discover it on their resources. Below on this page is the documentation of the armeria-scala library. Choose the most fittable way to integrate Armeria and Scala according to your demands and needs!

armeria-scala

The com.linecorp.armeria.scala package provides various useful extension methods and implicit conversions for an Armeria application written in Scala. To enable it, you first need the armeria-scala_2.12 or armeria-scala_2.13 dependency:

build.sbt
libraryDependencies += "com.linecorp.armeria" %% "armeria-scala" % "1.27.3"

and then import com.linecorp.armeria.scala.implicits._ in your Scala code:

import com.linecorp.armeria.scala.implicits._

Conversion between Java CompletionStage and Scala Future

You can convert a Java CompletionStage to a Scala Future using the toScala method:

import com.linecorp.armeria.common.HttpResponse
import com.linecorp.armeria.scala.implicits._
import com.linecorp.armeria.server.Server

import java.util.concurrent.CompletableFuture
import scala.concurrent.Future

val server =
  Server
    .builder()
    .service("/", (ctx, req) => HttpResponse.of(200))
    .build()

val javaFuture: CompletableFuture[Void] = server.start()
val scalaFuture: Future[Unit] = javaFuture.toScala // šŸ‘ˆ

You can also convert a Scala Future to a Java CompletionStage using the toJava method:

import java.util.concurrent.CompletionStage
val javaFuture: CompletionStage[Void] = scalaFuture.toJava // šŸ‘ˆ

You'll also find the extension method toHttpResponse added to Future[HttpResponse] and CompletionStage[HttpResponse] when converting an asynchronous result into an HttpResponse:

import com.linecorp.armeria.common.MediaType
import com.linecorp.armeria.scala.implicits._

val future: Future[String] = ...
val response =
  future
    .map { value => HttpResponse.of(MediaType.PLAIN_TEXT_UTF_8, value) }
    .toHttpResponse // šŸ‘ˆ

ExecutionContexts.sameThread

In an asynchronous system that the entire application logic runs on event loops, it is often useful in terms of performance to invoke the callbacks attached to a Future directly rather than submitting the callbacks to another ExecutionContext. You can use ExecutionContexts.sameThread in such a case:

import com.linecorp.armeria.common.HttpRequest
import com.linecorp.armeria.scala.ExecutionContexts.sameThread
import com.linecorp.armeria.scala.implicits._
import com.linecorp.armeria.server.HttpService
import com.linecorp.armeria.server.ServiceRequestContext

import scala.concurrent.ExecutionContext

class MyHttpService extends HttpService {
  override def serve(ctx: ServiceRequestContext, req: HttpRequest): HttpResponse = {
    implicit val ec: ExecutionContext = sameThread // šŸ‘ˆ

    // Perform some asynchronous operation.
    val future: Future[String] = ...

    // Convert the result to a response.
    future
      .map { value =>
        HttpResponse.of(MediaType.PLAIN_TEXT_UTF_8, value)
      }
      .toHttpResponse
  }
}

Context-aware ExecutionContext

You can use the eventLoopExecutionContext extension method in RequestContext to specify a Scala ExecutionContext that submits all tasks to the current context's event loop thread:

import com.linecorp.armeria.scala.implicits._

val ctx = ServiceRequestContext.current
implicit val ec: ExecutionContext = ctx.eventLoopExecutionContext // šŸ‘ˆ
Future {
  // Do some non-blocking job here.
}

For long-running tasks running on the server side, you can use blockingTaskExecutionContext:

import com.linecorp.armeria.scala.implicits._

val ctx = ServiceRequestContext.current
implicit val ec: ExecutionContext = ctx.blockingTaskExecutionContext // šŸ‘ˆ
Future {
  Thread.sleep(1000)
}

Collection converters

com.linecorp.armeria.scala.implicits._ will add toScala and toJava conversion extension methods to Java collections and Scala collections respectively. It means you don't need to import scala.jdk.CollectionConverters._ if you imported Armeria's implicits.

import com.linecorp.armeria.scala.implicits._
import com.linecorp.armeria.server.Server

val server: Server = ...
val scalaList = server.activePorts.toScala // šŸ‘ˆ

Implicit conversion between Java Duration and Scala FiniteDuration

A Scala FiniteDuration is implicitly converted into a Java Duration and vice versa for your convenience:

import com.linecorp.armeria.scala.implicits._
import scala.concurrent.duration._

Server
  .builder()
  .requestTimeout(5.seconds) // šŸ‘ˆ
  ...