1.9.0 release notes

23rd June 2021

🌟 New features

  • A new module armeria-graphql integrates with GraphQL Java to let you serve GraphQL requests. #3318 #3373

    ServerBuilder sb = ...;
    sb.service("/graphql",
        GraphqlService.builder()
                      .schemaFile(schemaFiles)
                      .runtimeWiring(w -> ...)
                      .configureDataLoaderRegistry(registry -> ...)
                      ...
                      .build());
  • You can now transform the objects of HttpRequest and HttpResponse using a function. #3606 #3624

    HttpRequest req1 = ...;
    HttpRequest req2 =
        req1.mapHeaders(headers -> headers.toBuilder()
                                          .add("TraceId", "1")
                                          .build())
            .mapData(data -> HttpData.ofUtf8(data.toStringUtf8() + '\n'))
            .mapTrailers(trailers -> trailers.toBuilder()
                                             .add("trailer-key", "val")
                                             .build())
    
    HttpResponse res1 = ...;
    HttpResponse res2 =
        res1.mapHeaders(headers -> headers.toBuilder()
                                          .add("SessionId", "123")
                                          .build())
            .mapInformational(informational -> {
                return informational.withMutations(builder -> {
                    builder.add(HttpHeaderNames.USER_AGENT, "Armeria");
                });
            })
            .mapData(data -> HttpData.ofUtf8(data.toStringUtf8()
                                     .replaceAll("\n", "<br/>")))
            .mapTrailers(trailers -> trailers.toBuilder()
                                             .add("result", "0")
                                             .build());
  • You can now collect the all elements of a StreamMessage with StreamMessage.collect(). #3603

    StreamMessage<Integer> stream = StreamMessage.of(1, 2, 3);
    CompletableFuture<List<Integer>> collected = stream.collect();
    assert collected.join().equals(List.of(1, 2, 3));
  • You can exclude a Route from another Route when configuring a service. #2737 #3562

    ServerBuilder sb = ...;
    sb.route()
      .pathPrefix("/api")
      .exclude("prefix:/api/admin")
      .exclude(Route.builder()
                    .pathPrefix("/api/v1")
                    .consumes(MediaType.JSON)
                    .build())
      .build(apiService);
    
    sb.routeDecorator()
      .pathPrefix("/api")
      .exclude("prefix:/api/admin")
      .build(MetricCollectingService.newDecorator());
  • You can now update the service bindings without restarting a Server using Server.reconfigure(). #3041 #3362 Note that there are some limitations on the Server reconfiguration. Please check out #3041 for details.

    Server server = ...;
    server.reconfigure(sb -> {
       sb.service("/dynamic", new LazyService());
    });
  • You can now use RequestOptions to configure various options at request level. #3593

    HttpRequest req = ...;
    WebClient client = ...;
    RequestOptions options =
        RequestOptions.builder()
                      .responseTimeout(Duration.ofSeconds(20))
                      .writeTimeout(Duration.ofSeconds(5))
                      .maxResponseLength(2048)
                      .attr(foo, "bar")
                      .build();
    // Specify RequestOptions when sending a request.
    client.execute(req, options);
    
    // You can also specify the options when building a request
    // using WebClientRequestPreparation.
    client.prepare()
          .get("/some/path")
          .responseTimeout(Duration.ofSeconds(20))
          .writeTimeout(Duration.ofSeconds(5))
          .maxResponseLength(2048)
          .attr(foo, "bar")
          .execute();
  • You can now fluently build a BlockingTaskExecutor using BlockingTaskExecutorBuilder. #3315 #3389

    BlockingTaskExecutor.builder()
                        .daemon(true)
                        .numThreads(500)
                        .threadNamePrefix("my-blocking-task-executor")
                        .build();
  • You can now easily get a boolean value from QueryParams and HttpHeaders. #3600

    HttpHeaders headers = HttpHeaders.of("checked", "true", "valid", "1");
    assert headers.getBoolean("checked");
    assert headers.getBoolean("valid");
    QueryParams params = QueryParams.of("checked", "false", "valid", "0");
    assert !params.getBoolean("checked");
    assert !params.getBoolean("valid");
  • You can now conveniently configure a loopback port with ServerBuilder.localPort(). #3599 #3601

    Server.builder()
          .localPort(8080)
          .localPort(9090, SessionProtocol.HTTP, SessionProtocol.HTTPS);
  • All Jackson modules in the class loader are registered automatically by default via Jackson's built-in SPI mechanism for your convenience.

    • You can override this behavior by registering your JacksonModuleProvider implementation via Java SPI. #3594 #3625
  • TCP user timeout option is now enabled by default when an idle timeout is enabled. #3509

    • TCP_KEEPIDLE and TCP_KEEPINTVL options are enabled when a PING interval is greater than 0.
  • You can now get the last value of a header or a query parameter using HttpHeaders.getLast() and QueryParams.getLast(). #3438 #3568

    HttpHeaders headers =
        HttpHeaders.builder()
                   .add(HttpHeaderNames.X_FORWARDED_FOR,
                       List.of("203.0.113.195", "150.172.238.178"))
                   .build();
    assert headers.getLast(HttpHeaderNames.X_FORWARDED_FOR)
                  .equals("150.172.238.178");
    
    QueryParams params =
        QueryParams.builder()
                   .add("items", List.of("1", "2", "3"))
                   .build();
    assert params.getLastInt("items").equals(3);
  • ContentTooLargeException now provides you the following 3 properties about why the exception was raised. #3520, #3616

    • The maximum allowed content length
    • The content length specified in the content-length header
    • The number of bytes transferred so far
  • You can now convert a Multipart into an HttpResponse. #3642

  • You can now warm up an EventLoopGroup using EventLoopGroups.warmUp(). #3363 #3610

  • You can now figure out the number of connections managed by a ClientFactory with ClientFactory.numConnections(). #3596 #3613

  • You can now specify the number of event loop threads and blocking task threads more conveniently. #3597 #3602

    Server.builder()
          .workerGroup(3)
          .blockingTaskExecutor(10)
          ...
          .build();
    
    ClientFactory.builder()
                 .workerGroup(3)
                 .build();
  • You can now clear the mocking state of a MockWebServerExtension using MockWebServerExtension.reset(). #3629

📈 Improvements

🛠️ Bug fixes

⛓ Dependencies

  • Bouncy Castle 1.68 → 1.69
  • Dropwizard 2.0.21 → 2.0.23
  • Dropwizard Metrics 4.1.21 → 4.2.2
  • Eureka 1.10.13 → 1.10.15
  • gRPC-Java 1.37.0 → 1.38.1
  • gRPC-Kotlin 1.0.0 → 1.1.0
  • JBoss logging 3.4.1 → 3.4.2
  • Jetty 9.4.39 → 9.4.42
  • Kotlin 1.5.0 → 1.5.10
  • Kotlin Coroutines 1.4.3 → 1.5.0
  • Netty 4.1.63 → 4.1.65
  • netty-tcnative-boringssl-static 2.0.38 → 2.0.39
  • Prometheus 0.10.0 → 0.11.0
  • Reactor 3.4.6 → 3.4.7
  • RESTEasy 4.6.0 → 4.6.1
  • RxJava 3.0.12 → 3.0.13
  • Scala
    • 2.12.13 → 2.12.14
    • 2.13.5 → 2.13.6
  • ScalaPB runtime 0.11.2 → 0.11.3
  • ScalaPB json4s 0.11.0 → 0.11.1
  • SLF4J 1.7.30 → 1.7.31
  • Spring 5.3.7 → 5.3.8
  • Spring Boot 2.4.5 → 2.5.1
  • Tomcat
    • 8.5.64 → 8.5.68
    • 9.0.44 → 9.0.46
  • Thrift 0.14.1 → 0.14.2

🙇 Thank you