1.16.0 release notes

19th April 2022

🌟 New features

  • Armeria Server now exposes the metrics about TLS handshake results. #4191

    armeria_server_tls_handshakes_total{
      cipher_suite=<the negotiated TLS cipher suite>,
      common_name=<the common name in TLS certificate>,
      protocol=<h1 or h2>,
      result=<success or failure>,
      tls_protocol=<TLS protocol name, e.g. TLSv1.3>
    }
  • You can use the @Delimiter annotation in an annotated service to specify the delimiter of a query parameter or header. #4060

    public class MyAnnotatedService {
      // Given the request:
      //   GET /api/v1/users?ids=1,2,3
      // Armeria will inject:
      //   List.of(1, 2, 3)
      // into:
      //   List<Integer> ids
      @Get("/api/v1/users")
      public List<User> getUsers(@Param @Delimiter(",") List<Integer> ids) {
        ...
      }
    }
  • You can now use the no-op subscribe() to drain elements from a StreamMessage #4145 #4185

    // The following statement prints 1, 2 and 3.
    StreamMessage
      .of(1, 2, 3)
      .peek(value -> System.out.println(value))
      .subscribe().join(); // 👈👈👈
  • You can now use the response_body option in gRPC-JSON transcoding. #4052 #4132 #4174

    • This option is useful when you need to make a certain response field as a response body. For example, the following Protobuf definition allows you to send a JSON array body whose values are sourced from GetNumbersResponse.myNumberArray:

      service MyService {
        rpc GetNumbers(GetNumbersRequest) returns (GetNumbersResponse) {
          option (google.api.http) = {
            get: "/api/v1/numbers"
            response_body: "my_number_array" // 👈👈👈
          };
        }
      }
      
      message GetNumbersRequest {}
      message GetNumbersResponse {
        repeated string my_number_array = 1; // 👈👈👈
      }
  • You can now use the @Decorator annotation to apply decorators to your gRPC stubs. #3967 #4041

    @LoggingDecorator // 👈👈👈
    public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
      @Override
      @RateLimitingDecorator(/* requestsPerSec */ 10.0) // 👈👈👈
      public void sayHello(
        HelloRequest req,
        StreamObserver<HelloReply> responseObserver
      ) {
        responseObserver.onNext(
          HelloReply
            .newBuilder()
            .setMessage(req.getName())
            .build()
        );
        responseObserver.onCompleted();
      }
    }
  • AbstractUnaryGrpcService and unframed GrpcService now include the stack trace for failed requests when verboseResponses option is enabled for easier debuggability. #4114 #4171

  • You can now specify query parameters additively by calling WebClientRequestPreparation.queryParam() #4173

    // Send GET /api/v1/greet?foo=1&bar=2
    WebClient
      .of().prepare()
      .get("/api/v1/greet")
      .queryParam("foo", "1")
      .queryParams(Map.of("bar", "2")) // 👈👈👈
      .execute()
  • You can now override the global Flags by implementing your own FlagsProvider and loading it via Java SPI. #4093 #4151

    package com.example.providers;
    
    // Add the following text file to your classpath or JAR file:
    //
    // $ cat META-INF/services/com.linecorp.armeria.common.FlagsProvider
    // com.example.providers.MyFlagsProvider
    //
    public class MyFlagsProvider implements FlagsProvider {
      @Override
      public int priority() {
        // The provider with higher value will be evaluated first.
        return 100;
      }
    
      @Override
      public int numCommonBlockingTaskThreads() {
          return 100;
      }
    }
  • When using armeria-graphql, you can now retrieve the current ServiceRequestContext with GraphqlServiceContexts.get() #4201

    new DataFetcher<>() {
      @Override
      public String get(DataFetchingEnvironment env) throws Exception {
        ServiceRequestContext ctx = GraphqlServiceContexts.get(env);
        ...
      }
    }
  • You can now disable the warning message about masked routes when building a Server by specifying the -Dcom.linecorp.armeria.reportMaskedRoutes=false JVM option. #4086

📈 Improvements

  • armeria-graphql doesn't use deprecated graphql-java API anymore. #4201
  • Less verbose and clearer TLS handshake logging #4181

🛠️ Bug fixes

  • JSON-gRPC transcoding now works even if you bind a GrpcService with path prefix. #4086 #4215
    Server
      .builder()
      .route()
        .pathPrefix("/api") // 👈👈👈
        .build(
          GrpcService
            .builder()
            .enableHttpJsonTranscoding(true)
            .addService(...)
            .build()
        )
      .build()
  • You now get the HeaderListSizeException as expected when the length of request headers is too long. #4180
  • EurekaEndpointGroup now continues refreshing itself even if it failed to refresh once. #4206
  • HealthCheckedEndpointGroup now leaves a detailed error message if all endpoints are unhealthy on the first check. #4200
  • Fixed a rare infinite loop in EndpointSelectionStrategy.rampingUp() #4219
  • The search domain DNS resolver no longer incorrectly resolves a search domain that starts with a dot. #4195 #4196
  • Fixed a bug where Armeria server doesn't log the remote address when TLS handshake fails. #4181
  • Reduced the cardinality of TLS certificate expiry metrics by using a hostname pattern instead of a hostname as a label. #4191
  • TomcatService now records the request start time of CoyoteRequest correctly. #4182 #4183
  • You don't get an exception anymore when parsing a multipart request with an empty body. #4175 #4179
  • You don't get the noisy warning message about double-completion of future, which occurs when a user misconfigured a ClientFactory #4191

📃 Documentation

  • Our build script examples in the website now include Gradle Kotlin DSL. #4106 #4176

🏚️ Deprecations

☢️ Breaking changes

  • The TLS certificate expiry metrics were renamed from armeria.server.certificate.* to armeria.server.tls.certificate.* for consistency. #4191
    • Also, the hostname label has been replaced with hostname_pattern to reduce the cardinality.
  • The return type of Flags.requestContextStorageProvider() was changed from String to RequestContextStorageProvider, so that a user knows which provider will be used exactly. #4093 #4151

⛓ Dependencies

  • Brave 5.13.7 → 5.13.8
  • Brotli4j 1.6.0 → 1.7.1
  • Bucket4j 7.3.0 → 7.4.0
  • Dropwizard 2.0.28 → 2.0.29
  • gRPC 1.45.0 → 1.45.1
  • Jackson 2.13.2 → 2.13.2.1
  • java-jwt 3.19.0 → 3.19.1
  • Jetty 9.4.45 → 9.4.46
  • Kotlin 1.6.10 → 1.6.20
    • Coroutines 1.6.0 → 1.6.1
  • Micrometer 1.8.4 → 1.8.5
  • Netty 4.1.75 → 4.1.76
  • Reactor 3.4.16 → 3.4.17
  • Scala 3.1.1 → 3.1.2
  • Spring Boot 2.6.5 → 2.6.6
  • Tomcat 9.0.56 → 9.0.62, 8.5.77 → 8.5.78

🙇 Thank you