1.3.0 release notes
30th November 2020
🌟 New features
You can now use io_uring for efficient I/O processing in Linux. #3182
- Specify the 
-Dcom.linecorp.armeria.transportType=io_uringJVM option to enable it. - Netty's 
io_uringtransport is currently experimental, so you should be careful using the feature. 
- Specify the 
 The metrics of requests to a
TransientServiceare not collected anymore by default. #3061 #3081- Access logs and service logs are not recorded as well.
 - You should use 
TransientServiceOptionto enable them.HealthCheckService.builder() .transientServiceOptions(TransientServiceOption.WITH_METRIC_COLLECTION, TransientServiceOption.WITH_SERVICE_LOGGING, TransientServiceOption.WITH_ACCESS_LOGGING) .build(); - Currently, 
HealthCheckServiceandPrometheusExpositionServiceareTransientServices. 
You can now use Protobuf's
Messageand ScalaPB'sGeneratedMessageas a request/response object in an annotated service. #3088 #3124 #3192- Use 
armeria-protobuf,armeria-scalapb_2.12orarmeria-scalapb_2.13dependencies. - See Supporting ScalaPB in annotated services for more information.
 
- Use 
 You can now use Scala
Futurein an annotated service. #3189@Get("/items/{id}") def items(@Param id: Int)(implicit ec: ExecutionContext): Future[String] = { Future { // Perform asynchronous task using Armeria's event loop. ... } }You can now use
HttpDeframerto conveniently decode a stream ofHttpObjectsto N objects. #2981HttpDeframerHandler<String> decoder = ... HttpDeframer<String> deframer = HttpDeframer.of(decoder, ByteBufAllocator.DEFAULT); HttpRequest request = ...; request.subscribe(deframer);- See 
HttpDeframerfor more information. 
- See 
 You can now apply
CircuitBreakerper request path. #3134, #3135CircuitBreakerFactory factory = ... // CircuitBreaker is applied per the combination of host and path. CircuitBreakerMapping mapping = CircuitBreakerMapping.builder() .perPath() .perHost() .build(factory); CircuitBreakerRule rule = ... CircuitBreakerClient.newDecorator(mapping, rule);You can now apply the different
maxTotalAttemptsandresponseTimeoutforRetryingClientusingRetryConfig. #3145BiFunction<ClientRequestContext, Request, String> keyFactory = (ctx, req) -> ctx.endpoint().host(); BiFunction<ClientRequestContext, Request, RetryConfig<HttpResponse>> configFactory = (ctx, req) -> { String host = ctx.endpoint().host(); RetryConfigBuilder builder = RetryConfig.<HttpResponse>builder(RetryRule.onException()); if (host.equals("host1")) { builder.maxTotalAttempts(2); } else if (host.equals("host2")) { builder.maxTotalAttempts(4); } else { builder.maxTotalAttempts(1); } return builder.build(); }; RetryConfigMapping mapping = RetryConfigMapping.of(keyFactory, configFactory); RetryingClient.newDecoratorWithMapping(mapping);You can now split the
ResponseHeadersand bodies usingHttpResponse.split(). #3038HttpResponse response = ... SplitHttpResponse splitHttpResponse = response.split(); CompletableFuture<ResponseHeaders> headersFuture = splitHttpResponse.headers(); StreamMessage<HttpData> bodyStream = splitHttpResponse.body(); headersFuture.thenApply(headers -> { if (headers.contentType() == MediaType.JSON_SEQ) { // Subscribe to a stream of HttpData. Flux.from(bodyStream) .map(httpData -> { // Convert HttpData to your domain object }); ... } });You can now customize for mapping an exception to a gRPC status. #3197
GrpcService.builder() .addExceptionMapping(AccessDeniedException.class, Status.UNAUTHENTICATED); // Or, use GrpcStatusFunction. GrpcService.builder() .exceptionMapping(cause -> { if (cause instanceof AccessDeniedException) { return Status.UNAUTHENTICATED; } if (cause instanceof FileNotFoundException) { return Status.NOT_FOUND; } return null; // Return null to use Armeria's default exception mapping. });You can now specify the Caffeine spec for the DNS resolver cache. #2970 #3007
You can now specify a prefix for MDC keys using the
<prefix>element. #3086 #3112<configuration> ... <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender"> ... <!-- set the prefix of exports which is not wrapped with the <exportGroup> element --> <prefix>armeria.</prefix> <export>remote.id</export> <export>req.headers.user-agent</export> ... <exportGroup> <!-- set the prefix of exports in this <exportGroup> --> <prefix>some_prefix.</prefix> <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export> ... </exportGroup> <exportGroup> <!-- if <prefix> is not defined, no prefix is added to exports --> <export>tracking_id=attr:com.example.AttrKeys#TRACKING_ID_KEY</export> ... </exportGroup> </appender> ... </configuration>You can now use the unsafe TLS cipher using
ClientFactoryBuilder.tlsAllowUnsafeCiphers(). #3157 #3172You can now specify an arbitrary type for
@Headerand@Paramif the type has one of following static methods or the constructor. #2574 #3143 #3164public static T of(String) { ... }public static T valueOf(String) { ... }public static T fromString(String) { ... }public T(String) { ... } // constructor
public class UserService { @Get("/api") public HttpResponse get(@Param User user) { ... } private static class User { User(String userId) { ... } // This constructor is used to create User. ... } }You can now build and execute an
HttpRequestfluently. #3110// Creates a POST HttpRequest whose URI is "/foo?q=bar" // with headers "cookie: name=value" and "authorization: value" and a JSON body. HttpRequest.builder() .post("/{resource}") .pathParam("resource", "foo") .queryParam("q", "bar") .cookie(Cookie.of("name", "value")) .header("authorization", "value") .content(MediaType.JSON, "{\"foo\":\"bar\"}")); // You can also use WebClient.prepare(). WebClient client = ... client.prepare() .post("/{resource}") .pathParam("resource", "foo") .queryParam("q", "bar") .cookie(Cookie.of("name", "value")) .header("authorization", "value") .content(MediaType.JSON, "{\"foo\":\"bar\"}") .execute();You can now easily handle cookies by applying
CookieClient.newDecorator(). #2637 #3118WebClient client = WebClient.builder() .factory(factory) .decorator(CookieClient.newDecorator()) .build(); client.get(...); // The cookies that are received from the origin server // are added to the request headers.You can now use the custom Thrift protocol by using
ThriftProtocolFactoryProviderand SPI. #3183public class TTupleFactoryProvider extends ThriftProtocolFactoryProvider { @Override public Set<ThriftProtocolFactoryProvider.Entry> entries() { return ImmutableSet.of(new ThriftProtocolFactoryProvider.Entry( SerializationFormat.of("ttuple"), new TTupleProtocol.Factory())); } }You can now collect more detailed DNS metrics. #1887 #2935
armeria.client.dns.queries#count{...,result=success}armeria.client.dns.queries#count{...,result=failure}armeria.client.dns.queries.written#count{...}armeria.client.dns.queries.cancelled#count{...}armeria.client.dns.queries.redirected#count{...}armeria.client.dns.queries.cnamed#count{...}armeria.client.dns.queries.noanswer#count{...}
You can now customize the
HealthCheckServicewhen using Spring integration. #3144@Bean public HealthCheckServiceConfigurator healthCheckServiceConfigurator() { return builder -> builder.updatable(true); }You can now use
RequestHeaders.acceptLanguage()to choose language. #3177 #3179
📈 Improvements
- Various improvements for 
DocService. #3149 #3150 #3167 #3188 
🛠️ Bug fixes
HealthCheckedEndpointGroup.endpoints()now returns healthy endpoints properly even whenEndpointGroup.orElse()is used. #3181ServletRequest.getProtocol()now returns the proper value when usingTomcatServiceandJettyService. #3194- The route decorators are now evaluated in the reverse order they applied. #3160 #3166
- The purpose of this change is to make sure we provide a consistent experience when decorating a service.
For example, when we decorate a service like the following:we expect
myService .decorate(decoratorA) .decorate(decoratorB);decoratorAdecoratesmyServiceanddecoratorBdecoratesdecoratorA. Route decorators were breaking this expectation. 
 - The purpose of this change is to make sure we provide a consistent experience when decorating a service.
For example, when we decorate a service like the following:
 - You now get the FORBIDDEN status if your service does not handle preflight requests regardless of route decorators. #3152
 - A gRPC 
ServerCallis now closed exactly only once. #3153 - You no longer see 
AnnotatedConnectExceptionwhen theEndpointis created with an IPv6 scope ID. #3158 #3178 - Armeria server does not reject the request path whose first segment includes a colon anymore. #3154
 
🏚️ Deprecations
CircuitBreakerClient.newPerHostAndMethodDecorator()is now deprecated. #3135- Use 
CircuitBreakerClient.newDecorator()with the customizedCircuitBreakerMappingusingCircuitBreakerMapping.builder(). 
- Use 
 - The response timeout and max total attempts setters in 
RetryingClientBuilderare now deprecated. #3128 #3145- The static factory methods that take those parameters in 
RetryingClientare now deprecated as well. - Use 
RetryConfigMappingandRetryConfig. 
 - The static factory methods that take those parameters in 
 Route.apply(RoutingContext)is deprecated. #3152- The constructor of 
PrometheusExpositionServiceis now deprecated. #3081 
☢️ Breaking changes
- Route decorators are now evaluated in the reverse order they applied. #3160 #3166
- The purpose of this change is to make sure we provide a consistent experience when decorating a service.
For example, when we decorate a service like the following:we expect
myService .decorate(decoratorA) .decorate(decoratorB);decoratorAdecoratesmyServiceanddecoratorBdecoratesdecoratorA. Route decorators were breaking this expectation. 
 - The purpose of this change is to make sure we provide a consistent experience when decorating a service.
For example, when we decorate a service like the following:
 
⛓ Dependencies
- Dropwizard 2.0.13 → 2.0.16
 - Fastutil 8.4.2 → 8.4.3
 - gRPC 1.33.0 → 1.33.1
 - grpc-kotlin-stub 0.2.0 → 0.2.1
 - Dropwizard Metrics 4.1.13 → 4.1.15
 - Jackson 2.11.2 → 2.12.0
 - JCTools 3.1.0 → 3.2.0
 - javax.annotation-api 1.3.2
- jakarta-annotation-api 2.0 has been released with a breaking change so we use javax.annotation-api instead.
 
 - Micrometer 1.5.5 → 1.6.1
 - Netty 4.1.53.Final → 4.1.54.Final
 - BouncyCastle 1.66 → 1.67
 - Reactor 3.3.10.RELEASE → 3.4.0
 - Spring Boot 2.3.4.RELEASE → 2.4.0
 - Spring 5.2.9.RELEASE → 5.3.1
 - Tomcat 9.0.39 → 9.0.40