Spring Boot integration

The Spring framework provides powerful features necessary for building web applications such as dependency injection, data binding, AOP and transaction management. By integrating your Spring application with Armeria, you can serve both legacy Spring services and gRPC or Thrift services on the same port. Additionally, you can gradually migrate your services to Armeria for improved performance.

Armeria provides a simple way to integrate legacy Spring Boot applications with just a few lines of code. Furthermore, Armeria supports several internal services for monitoring and management purposes, which can be conveniently configured through beans.

Integrating with Spring MVC (with Tomcat)

You can integrate your Spring MVC with Armeria using TomcatService. First, add the following dependency to your application.

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

    ...
    implementation 'com.linecorp.armeria:armeria-spring-boot3-starter'
    implementation 'com.linecorp.armeria:armeria-tomcat10'
}

Serve the embedded TomcatService via Armeria by using the ArmeriaServerConfigurator bean.

ArmeriaConfiguration.java
import com.linecorp.armeria.server.tomcat.TomcatService;
import com.linecorp.armeria.spring.ArmeriaServerConfigurator;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ArmeriaConfiguration {
   public static Connector getConnector(ServletWebServerApplicationContext applicationContext) {
       final TomcatWebServer container = (TomcatWebServer) applicationContext.getWebServer();
       container.start();
       return container.getTomcat().getConnector();
   }

   @Bean
   public TomcatService tomcatService(ServletWebServerApplicationContext applicationContext) {
       return TomcatService.of(getConnector(applicationContext));
   }

   @Bean
   public ArmeriaServerConfigurator armeriaServerConfigurator(TomcatService tomcatService) {
       return sb -> sb.serviceUnder("/", tomcatService);
   }
}

Add the following properties to your application.yml (or application.properties) file. This configuration will prevent the exposure of the embedded Tomcat service and instead expose your Armeria service. You can explore additional configuration options in ArmeriaSettings.

application.yml
# Prevent the embedded Tomcat from opening a TCP/IP port.
server:
  port: -1

armeria:
  ports:
    - port: 8080
      protocols:
        - HTTP

With this simple configuration, everything is set up. Now you can serve your legacy Spring service through Armeria. Although it may not seem like much has changed compared to before, your application is now ready to serve both RPC services (such as gRPC or Thrift) and RESTful services via Armeria.

Integrating with Spring Boot

Even if you don't utilize Spring MVC based on web servlets like Tomcat, there is still significant value in combining Spring and Armeria. You can leverage the automatic configuration and dependency injection features of Spring Boot to develop an Armeria application.

First, you need the armeria-spring-boot3-starter dependency:

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

    ...
    implementation 'com.linecorp.armeria:armeria-spring-boot3-starter'
}

Armeria's annotated service is similar to a Spring controller. You can declare the annotated service as a bean and inject it into the ServerBuilder. Here's an example:

TodoAnnotatedService.java
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.server.annotation.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

// Make this annotated service a bean.
@Component
@PathPrefix("/todos")
public class TodoAnnotatedService {

    private final TodoRepository todoRepository;

    // Automatically injected by Spring
    @Autowired
    public TodoAnnotatedService(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    @Get("/:id")
    public HttpResponse get(@Param Integer id) {
        Todo todo = todoRepository.get(id);
        if (todo == null) {
            return HttpResponse.of(HttpStatus.NO_CONTENT);
        }
        return HttpResponse.ofJson(todo);
    }

    @Post
    public HttpResponse create(Todo todo) {
        final int result = todoRepository.create(todo);
        if (result == 0) {
            return HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return HttpResponse.of(HttpStatus.CREATED);
    }
}

To inject the annotated service into the ArmeriaServerConfigurator bean, you can follow this approach:

ArmeriaConfiguration.java
@Bean
public ArmeriaServerConfigurator armeriaServerConfigurator(TodoAnnotatedService todoAnnotatedService) {
   return serverBuilder -> {
       serverBuilder.serviceUnder("/docs", new DocService())
                    .decorator(LoggingService.newDecorator())
                    .annotatedService("/api", todoAnnotatedService);
   };
}

The following configuration prevents the execution of Spring's embedded web server and instead runs Armeria.

application.yml
# See https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.disable.
# The application should not run as a web application and should not start an embedded web server.
spring.main.web-application-type: none

armeria:
  ports:
    - port: 8080
      protocols:
        - http

Internal services

Armeria supports four internal services that are useful for monitoring and management purposes. You can enable these services by including their service IDs in your application.yml (or application.properties) file. Service ids are docs, health, metrics, and actuator. You can include one or multiple service IDs based on your requirements.

application.yml
armeria:
  ports:
    - port: 8080
      protocols:
        - HTTP
  # Add the below configuration
  internal-services:
    include: docs, health, metrics, actuator  # you can add all of the services using `all`
    port: 8090

The armeria.internal-services.port configuration is not necessary. If the port is not specified or left as 0, Armeria will automatically bind the internal service to a random unused port. It is acceptable to set the port of the internal service to be the same as one of the armeria.ports.

Documentation service

Armeria has its own documentation service that offers useful features. For instance, it allows you to test RPC protocols in a web browser console, similar to Swagger.

Add the service ID docs to armeria.internal-services.include configuration. The armeria.docs-path is not necessary, as the default path for the documentation service is /internal/docs.

application.yml
armeria:
  internal-services:
    include: docs
  docs-path: /internal/docs

To add custom configuration to your documentation service, you can utilize the DocServiceConfigurator bean.

ArmeriaConfiguration.java
@Bean
public DocServiceConfigurator docServiceConfigurator() {
    return docServiceBuilder -> docServiceBuilder
            .exampleRequests(TodoAnnotatedService.class, "create", "{\"id\":\"42\", \"value\":\"foo bar\"}");
}

Health check

To customize the health check operation, you can utilize the HealthChecker bean.

Add the service ID health to the armeria.internal-services.include configuration. The armeria.health-check-path is not necessary, as the default path for the health check service is /internal/healthcheck.

application.yml
armeria:
  internal-services:
    include: health
  health-check-path: /internal/healthcheck

You can create a HealthChecker bean that implements your custom health check logic. For example, the below code determines if the server is healthy using Tomcat connector state.

ArmeriaConfiguration.java
@Bean
public HealthChecker tomcatConnectorHealthChecker(ServletWebServerApplicationContext applicationContext) {
    final Connector connector = getConnector(applicationContext);
    return () -> connector.getState().isAvailable();
}

You can also add custom configuration to the service using the HealthCheckServiceConfigurator bean, similar to the documentation service.

ArmeriaConfiguration.java
@Bean
public HealthCheckServiceConfigurator healthCheckServiceConfigurator() {
    return healthCheckServiceBuilder -> healthCheckServiceBuilder
            .updatable(true)
            .startUnhealthy();
}

Collecting metrics

Armeria provides a built-in metric service using the Micrometer library. You can expose the collected metrics to various monitoring systems, such as Prometheus or Dropwizard for comprehensive monitoring and analysis.

To include the metrics service, simply add the service ID metrics to the armeria.internal-services.include configuration. You do not need to specify armeria.enable-metrics or armeria.metrics-path as the default path for the metrics service is /internal/metrics.

application.yml
armeria:
  internal-services:
    include: metrics
  metrics-path: /internal/metrics
  enable-metrics: true  # default is true

We will illustrate the bean configuration using an example with the Prometheus monitoring system. First, create a PrometheusMeterRegistry bean. If you are using a different monitoring system, create a bean of MeterRegistry type.

ArmeriaConfiguration.java
@Bean
public PrometheusMeterRegistry prometheusMeterRegistry() {
    return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}

To add a prefix to the ID of collected metrics, you can use the MeterIdPrefixFunction bean. Additionally, you can customize the configuration of the service by utilizing the MetricCollectingServiceConfigurator bean.

ArmeriaConfiguration.java
@Bean
public MeterIdPrefixFunction meterIdPrefixFunction() {
    return MeterIdPrefixFunction.ofDefault("my.armeria.service");
}

@Bean
public MetricCollectingServiceConfigurator metricCollectingServiceConfigurator() {
    return metricCollectingServiceBuilder -> metricCollectingServiceBuilder
            .successFunction((context, log) -> {
                final int statusCode = log.responseHeaders().status().code();
                // Treat a 404 response as a success
                return statusCode >= 200 && statusCode < 400 || statusCode == 404;
            });
}

Actuator support

If you are using Spring Boot Actuator, you can serve it at the armeria.internal-services.port. The internal services may also be served at the management.server.port if specified.

In order to use the actuator service, you need to first add the following dependencies.

build.gradle
dependencies {
    ...
    implementation 'com.linecorp.armeria:armeria-spring-boot3-actuator-starter'
}

To include the actuator service, add the service ID actuator to the armeria.internal-services.include configuration.

application.yml
armeria:
  internal-services:
    include: actuator

Other bean configurations

We will provide an explanation of some additional bean configurations that can be helpful to know.

ArmeriaServerConfigurator vs Consumer<ServerBuilder>

You can use both ArmeriaServerConfigurator and Consumer<ServerBuilder> to configure the server using ServerBuilder. In fact, both are essentially same functions of ServerBuilder -> void. The most significant difference lies in the order in which they are applied to the server. Armeria first configures all ArmeriaServerConfigurator beans and subsequently applies all Consumer<ServerBuilder> beans.

If you have multiple ArmeriaServerConfigurator or Consumer<ServerBuilder> beans, you can set the order using the @Order annotation. It is important to note that the default order of ArmeriaServerConfigurator is zero, and not Ordered.LOWEST_PRECEDENCE.

DependencyInjector

Armeria provides the ability to manually inject dependencies using DependencyInjector. You can refer to the example in the 1.17.0 release notes for more details.

Create the DependencyInjector bean, which will replace the default dependency injector. It's important to note that in this case dependencies that were automatically injected before may not be injected anymore.

ArmeriaConfiguration.java
@Bean
public DependencyInjector dependencyInjector() {
    return DependencyInjector.ofSingletons(
            new BadRequestExceptionHandler(),
            new AuthDecorator((ctx, req) ->
                    CompletableFuture.supplyAsync(() -> req.headers().get(AUTHORIZATION).equals("auth-token"))
            )
    );
}

You can also utilize a dependency injector that leverages the BeanFactory of Spring. After configuring the properties, you can effortlessly create beans to be injected, similar to how you would do it in a Spring-based setup.

application.yml
armeria:
  enable-auto-injection: true