1.10.0 release notes

19th August 2021

🌟 New features

  • A new module armeria-sangria that integrates with Sangria GraphQL is introduced to let you serve a GraphQL request in Scala. #3703 #3704

    val schema: Schema[CharacterRepo, Unit] = Schema(Query)
    Server.builder()
          .service("/graphql",
              SangriaGraphqlService.builder(schema, new CharacterRepo)
                                   .enableTracing(true)
                                   .maxQueryDepth(10)
                                   .build())
  • You can now configure WebClient to automatically follow redirections. #2489 #3641

    WebClient.builder()
             .followRedirects()
             .build();
    // Customize redirection policy
    RedirectConfig config = RedirectConfig.builder()
                                          .maxRedirects(10)
                                          .allownDomains("foo.com", "bar.com")
                                          .build();
    WebClient.builder("https://example.com")
             .followRedirects(config)
             .build();
  • You can now recover a failed HttpResponse with a fallback HttpResponse. It would be useful for handling an error of HttpResponse in a decorator. #3674

    HttpResponse response =
        HttpResponse.ofFailure(new IllegalStateException("Oops..."));
    // The failed HttpResponse will be recovered by the fallback function.
    HttpResponse recovered =
        response.recover(cause -> HttpResponse.of("Fallback"));
    assert recovered.aggregate().join().contentUtf8().equals("Fallback");
  • You can now resume a failed StreamMessage with a fallback StreamMessage. #3674

    DefaultStreamMessage<Integer> stream = new DefaultStreamMessage<>();
    stream.write(1);
    stream.write(2);
    stream.close(new IllegalStateException("Oops..."));
    StreamMessage<Integer> resumed =
        stream.recoverAndResume(cause -> StreamMessage.of(3, 4));
    assert resumed.collect().join().equals(List.of(1, 2, 3, 4));
  • You can now transform an error of StreamMessage into another using StreamMessage.mapError(). #3668

    StreamMessage stream =
        StreamMessage.aborted(ClosedStreamException.get());
    StreamMessage transformed = stream.mapError(ex -> {
      if (ex instanceof ClosedStreamException) {
        return new IllegalStateException(ex);
      } else {
        return ex;
      }
    });
  • You can now automatically encode an object into JSON for a response payload using HttpResponse.ofJson(). #3662

    MyObject myObject = ...;
    HttpResponse.ofJson(myObject);
    MyError myError = ...;
    HttpResponse.ofJson(HttpStatus.INTERNAL_SERVER_ERROR, myError);
  • You can now fluently inject io.grpc.ServerInterceptors using GrpcServiceBuilder.intercept().

    GrpcService.builder()
               .addService(myService)
               .intercept(myInterceptror)
               .build();
  • You can now easily add Accept headers with RequestHeadersBuilder.accept(). #3704

    RequestHeaders.builder()
                  .accept(MediaType.JSON)
                  .accept(MediaType.PLAIN_TEXT);
  • You can now initiate graceful connection shutdown using ServiceRequestContext.initiateConnectionShutdown(). #3516 #3715

  • You can now specify the default ObjectMapper using JacksonObjectMapperProvider. #3728

    public class MyObjectMapperProvider
        implements JacksonObjectMapperProvider {
    
      @Override
      public ObjectMapper newObjectMapper() {
        return JsonMapper
           .builder()
           .addModules(new KotlinModule())
           .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
           .build();
      }
    }
  • You can now serve application/grpc-web+proto and application/grpc-web-text+proto protocols using AbstractUnaryGrpcService. #3638 #3716

  • gRPC trailers are available in the RequestContext for unary gRPC server and client. #3724 #3739

    UnaryGrpcClient client = ...;
    try (ClientRequestContextCaptor captor = Clients.newContextCaptor()) {
        client.execute("/com.example.MyService/UnaryCall", request);
        ClientRequestContext ctx = captor.get();
        HttpHeaders trailers = GrpcWebTrailers.get(ctx); // 👈👈👈
    }
  • You can now compress and decompress content with Brotli when using EncodingService, DecodingService and DecodingClient. #3544 #3686

  • You can now create an HttpResponseException with a cause. #3674

  • You can now easily capture ServiceRequestContexts using ServerExtension in tests. #3648

  • You can now suppress the inconsistent Netty version warning by specifying -Dcom.linecorp.armeria.warnNettyVersions=false. #3572 #3766

📃 Documentation

  • You can now learn how to write a REST service with Armeria by walking through the brand-new tutorials. #3420
    • Special thanks to @freevie who volunteered for this.

📈 Improvements

🛠️ Bug fixes

🏚️ Deprecations

☢️ Breaking changes

  • ExceptionHandler now returns HttpResponse instead of AggregatedHttpResponse. #3674

    // Before:
    ExceptionHandler handler = (ctx, cause) -> {
        if (cause instanceof IllegalArgumentException) {
            return AggregatedHttpResponse.of(HttpStatus.BAD_REQUEST);
        }
        return null;
    }
    
    // After:
    ExceptionHandler handler = (ctx, cause) -> {
        if (cause instanceof IllegalArgumentException) {
            return HttpResponse.of(HttpStatus.BAD_REQUEST); // 👈👈👈
        }
        return null;
    }
  • RequestContext is added as the first parameter of GrpcStatusFunction. #3692 #3693

    // Before:
    GrpcService.builder()
               .exceptionMapping((throwable, metadata) -> {
                   if (throwable instanceof IllegalArgumentException) {
                       return Status.INVALID_ARGUMENT;
                   }
                   return null;
               });
    
    // After:
    GrpcService.builder()
               .exceptionMapping((ctx, throwable, metadata) -> { // 👈👈👈
                   if (throwable instanceof IllegalArgumentException) {
                       return Status.INVALID_ARGUMENT;
                   }
                   return null;
                });
  • JacksonModuleProvider has been removed in favor of JacksonObjectMapperProvider.

  • ResponseConverterFunctionProvider.createResponseConverterFunction(Type,ResponseConverterFunction,ExceptionHandlerFunction) has been removed in favor of ResponseConverterFunctionProvider.createResponseConverterFunction(Type,ResponseConverterFunction) . #3674

  • OAuth2AuthorizationGrant.withAuthorization() has been removed. #3618

⛓ Dependencies

  • Curator 5.1.0 → 5.2.0
  • Dropwizard 2.0.23 → 2.0.24
  • Dropwizard Metrics 4.2.2 → 4.2.3
  • Jackson 2.12.3 → 2.12.4
  • Jetty 9.4.42 → 9.4.23
  • GraphQL-Java 16.2 → 17.1
  • gRPC-Java 1.38.1 → 1.40.0
  • Kotlin 1.5.10 → 1.5.21
  • kotlinx-coroutines-core 1.5.0 → 1.5.1
  • Logback 1.2.3 → 1.2.5
  • Micrometer 1.7.1 → 1.7.2
  • Netty 4.1.65 → 4.1.66
  • netty-tcnative-boringssl-static 2.0.39 → 2.0.40
  • netty-transport-native-io_uring 0.0.5 → 0.0.8
  • java-jwt 3.16.0 → 3.18.1
  • protobuf-java 3.12.0 → 3.17.2
  • Reactor 3.4.7 → 3.4.9
  • RESTeasy 4.6.1 → 4.7.1
  • RxJava 3.0.13 → 3.1.0
  • scala-collection-compat 2.4.4 → 2.5.0
  • ScalaPB 0.11.4 → 0.11.5
  • ScalaPB json4s 0.11.1 → 0.12.0
  • SLF4J 1.7.31 → 1.7.32
  • Spring 5.3.8 → 5.3.9
  • Spring Boot 2.5.2 → 2.5.3
  • ZooKeeper 3.6.2 → 3.6.3

🙇 Thank you