1.14.0 release notes

27th January 2022

🌟 New features

  • You can now use Scala 3 for armeria-scala and armeria-scalapb. #3614 #4036

  • You can now fluently convert an HttpResponse into a desired type using WebClient. #4021

    WebClient client = WebClient.of("https://api.example.com");
    CompletableFuture<ResponseEntity<MyObject>> response =
      client.prepare()
            .get("/v1/items/1")
            .asJson(MyObject.class)
            .execute();
  • You can now use BlockingWebClient to wait for an HttpResponse to be completed. #4021

    BlockingWebClient client = BlockingWebClient.of("https://api.example.com");
    ResponseEntity<MyObject> response =
      client.prepare()
            .get("/v1/items/1")
            .asJson(MyObject.class)
            .execute();
  • You can now fluently build a gRPC client with various options using GrpcClientBuilder. #3981 #3999 #3964 #3975

    GrpcClients
      .builder(...)
      .decorator(myDecorators)
      .maxResponseMessageLength(MAX_MESSAGE_SIZE)
      .jsonMarshallerFactory(descriptor -> {
        ...
      })
      .intercept(myInterceptors)
      .build(MyStub.class);
  • You can now fluently build an HttpResponse by using HttpResponseBuilder. #3398 #3941

    HttpResponse
      .builder()
      .ok()
      .header(HttpHeaderNames.USER_AGENT, "Armeria")
      .content(content)
      .build();
  • You can now use StreamMessage.mapAsync() to transform a StreamMessage using an async operation. #3916 #3962

    StreamMessage<Integer> userIds = StreamMessage.of(...);
    StreamMessage<UserInfo> userInfos = userIds.mapAsync(this::getUserInfo);
    
    CompletableFuture<UserInfo> getUserInfo(int userId) {
      ...
    }
  • You can now use HttpResponse.peekHeaders(), HttpMessage.peekData(), HttpMessage.peekTrailers() and StreamMessage.peekError() operators on HttpRequest and HttpResponse. #3097 #3988 #3949

    HttpRequest request =
      HttpRequest.of(RequestHeaders.of(HttpMethod.POST, "/items"),
                     HttpData.ofUtf8("data1,data2"))
                     HttpHeaders.of("trailer", "foo"));
    HttpRequest peeked =
      request.peekData(data -> {
        assert data.toStringUtf8().equals("data1,data2");
      }).peekTrailers(trailers -> {
        assert trailers.get("trailer").equals("foo");
      });
    
    HttpRequest failed =
      HttpRequest.ofFailure(new IllegalStateException("Something went wrong."));
    HttpRequest peeked = failed.peekError(cause -> {
      assert cause instanceof IllegalStateException;
    });
  • You can now generate an error response differently in ServerErrorHandler depending on the RequestHeaders. #4037

  • You can now use {*var} and :*var path patterns to capture rest paths of a request. #3031 #3997

    Server
      .builder()
      .service("/api/{item}/route/{*contents}", (ctx, req) -> {
        // If a request path is "/api/123/route/foo/bar/baz",
        // '*contents' should be foo/bar/baz
        assert ctx.pathParam("contents").equals("foo/bar/baz");
      });
  • You can now use ServiceRequestContext.queryParams() to get the decoded query parameters. #3955 #3960

  • You can now send query parameters using QueryParams with WebClient. #3915 #3921

    QueryParams params = QueryParams.of("foo", "bar");
    WebClient client = ...;
    // Sends 'GET /api/items/1?foo=bar'
    client.get("/api/items/1", params);
    // Sends 'POST /api/items?foo=bar' with body '...'
    client.post("/api/items", params, content);
  • You can now write a StreamMessage<HttpData> into a file using StreamMessages.writeTo(). #3870 #3874

    StreamMessage<HttpData> stream = ...;
    Path destination = ...;
    StreamMessages.writeTo(stream, destination);
  • You can now force a state transition of a CircuitBreaker by using CircuitBreaker.enterState(). #4010 #4022

    CircuitBreaker circuitBreaker = CircuitBreaker.of(...);
    circuitBreaker.enterState(CircuitState.FORCED_OPEN);
  • You can now conveniently create a RetryRule and RetryRuleWithContent by specifying a Backoff. #3875 #2899 #3895

    RetryRule.failsafe(Backoff.fixed(1000));
    RetryRuleWithContent.onException(Backoff.fixed(2000));
  • You can now dynamically change the maximum number of concurrent active requests of a ConcurrencyLimitingClient. #3842 #3985

    ConcurrencyLimitingClient.newDecorator(new DynamicLimit());
    
    class DynamicLimit implements IntSupplier {
      @Override
      public int getAsInt() {
        // Dynamically returns the current limitation.
        ...
      }
    }
  • You can now split an HttpRequest into RequestHeaders, StreamMessage<HttpData> and trailers using HttpRequest.split(). #3924 #3953

    HttpRequest request = HttpRequest.of(...);
    SplitHttpRequest splitRequest = request.split();
    
    RequestHeaders headers = splitRequest.headers();
    StreamMessage<HttpData> body = splitRequest.body();
    CompletableFuture<HttpHeaders> trailers = splitRequest.trailers();
  • Added more well-known MIME type constants to MediaType. #4040

  • You can now customize the MediaType of a file when using FileService. #4001 #4009

    FileService
      .builder(resource)
      .mediaTypeResolver((path, contentEncoding) -> {
        if (path.endsWith(".proto")) {
          return MediaType.PROTOBUF;
        }
        if (path.endsWith(".csv")) {
          return MediaType.CSV_UTF_8;
        }
        // Pass to the default resolver.
        return null;
      })
      .build();
  • You can now send requests to a test server by using ServerExtension.webClient() or ServerRule.webClient(). #3761 #3890

    @RegisterExtension
    static ServerExtension server = new ServerExtension() {
      @Override
      protected void configure(ServerBuilder sb) {
        ...
      }
    };
    
    @Test
    void test() {
      WebClient client = server.webClient(cb -> {  // 👈👈👈
        cb.decorator(LoggingClient.newDecorator());
      });
      client.get("/foo").aggregate().join();
    }
  • You can now export metrics as OpenMetrics format using PrometheusExpositionService. #3926 #3928

  • You can now use UnframedGrpcStatusMappingFunction to customize how a gRPC status is mapped to an HttpStatus when using an unframed gRPC service. #3683 #3948

    UnframedGrpcStatusMappingFunction mappingFunction =
      (ctx, grpcStatus, cause) -> {
        if (grpcStatus.getCode() == DEADLINE_EXCEEDED) {
          return INTERNAL_SERVER_ERROR;
        }
        // Pass to the default mapping function.
        return null;
      };
    
    UnframedGrpcErrorHandler errorHandler =
      UnframedGrpcErrorHandler.ofJson(mappingFunction);
    
    GrpcService
      .builder()
      .unframedGrpcErrorHandler(errorHandler);
  • You can now expose WebOperations of Actuator to a port of internal services. #3919 #3946

    armeria:
      ...
      internal-services:
        # Actuator will be exposed to 18080 port.
        include: actuator, health
        port: 18080
  • Added JettyServiceBuilder.tlsReverseDnsLookup() that makes JettyService perform a reverse DNS lookup for each TLS connection, which may be useful for servlets that assume ServletRequest.getRemoteHost() will return a remote host name rather than a remote IP address.

  • Added JettyServiceBuilder.httpConfiguration() that allows a user to set Jetty HttpConfiguration in a type-safe manner.

📈 Improvements

🛠️ Bug fixes

🏚️ Deprecations

☢️ Breaking changes

⛓ Dependencies

  • Brave 5.13.3 → 5.13.7
  • Bouncy Castle 1.69 → 1.70
  • Bucket4J 6.3.0 → 7.0.0
  • Caffeine 2.9.2 → 2.9.3
  • Dropwizard 2.0.25 → 2.0.28
  • Dropwizard Metrics 4.2.4 → 4.2.7
  • gRPC Java 1.41.1 → 1.43.2
  • gRPC Kotlin 1.1.0 → 1.2.1
  • Jackson 2.13.0 → 2.13.1
  • java-jwt 3.18.2 → 3.18.3
  • SLF4J 1.7.32 → 1.7.34
  • jboss-logging 3.4.2 → 3.4.3
  • Kafka 3.0.0 → 3.1.0
  • Kotlin 1.5.32 → 1.6.10
  • Logback 1.2.7 → 1.2.10
  • Micrometer 1.7.6 → 1.8.2
  • Netty 4.1.70 → 4.1.73
    • io_uring 0.0.1 → 0.0.11
  • Prometheus 0.12.0 → 0.14.1
  • protobuf-jackson 1.2.0 → 2.0.0
  • protobuf-java 3.17.3 → 3.19.2
  • Reactor 3.4.12 → 3.4.14
  • RESTEasy 4.7.3 → 5.0.2
  • Scala 2.13.7 → 2.13.8, ⓧ → 3.1.1
  • scala-collection-compat 2.5.0 → 2.6.0
  • ScalaPB 0.11.6 → 0.11.8
  • Spring 5.3.13 → 5.3.15
  • Spring Boot 2.5.7 → 2.6.3
  • Tomcat 9.0.55 → 9.0.56

🙇 Thank you