1.10.0 release notes
19th August 2021
🌟 New features
A new module
armeria-sangriathat integrates with Sangria GraphQL is introduced to let you serve a GraphQL request in Scala. #3703 #3704val schema: Schema[CharacterRepo, Unit] = Schema(Query) Server.builder() .service("/graphql", SangriaGraphqlService.builder(schema, new CharacterRepo) .enableTracing(true) .maxQueryDepth(10) .build())You can now configure
WebClientto automatically follow redirections. #2489 #3641WebClient.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
HttpResponsewith a fallbackHttpResponse. It would be useful for handling an error ofHttpResponsein a decorator. #3674HttpResponse 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
StreamMessagewith a fallbackStreamMessage. #3674DefaultStreamMessage<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
StreamMessageinto another usingStreamMessage.mapError(). #3668StreamMessage 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(). #3662MyObject myObject = ...; HttpResponse.ofJson(myObject); MyError myError = ...; HttpResponse.ofJson(HttpStatus.INTERNAL_SERVER_ERROR, myError);You can now fluently inject
io.grpc.ServerInterceptors usingGrpcServiceBuilder.intercept().GrpcService.builder() .addService(myService) .intercept(myInterceptror) .build();You can now easily add
Acceptheaders withRequestHeadersBuilder.accept(). #3704RequestHeaders.builder() .accept(MediaType.JSON) .accept(MediaType.PLAIN_TEXT);You can now initiate graceful connection shutdown using
ServiceRequestContext.initiateConnectionShutdown(). #3516 #3715You can now specify the default
ObjectMapperusingJacksonObjectMapperProvider. #3728public 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+protoandapplication/grpc-web-text+protoprotocols usingAbstractUnaryGrpcService. #3638 #3716gRPC trailers are available in the
RequestContextfor unary gRPC server and client. #3724 #3739UnaryGrpcClient 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,DecodingServiceandDecodingClient. #3544 #3686You can now create an
HttpResponseExceptionwith a cause. #3674You can now easily capture
ServiceRequestContextsusingServerExtensionin tests. #3648You 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
HttpHeadersnow properly caches well-known HTTP headers' value objects. #3711 #3714ConcurrencyLimitTimeoutExceptionis now raised when a request times out inConcurrencyLimitingClient. #3681OAuth2AuthorizationGrantno longer uses locks for better performance. #3571 #3618
🛠️ Bug fixes
- You no longer see an
IllegalArgumentExceptionwhenHttpRequest.aggregate()andHttpResponse.aggregate()fail with an exception. #3676 #3684 #3687 - You no longer see an incomplete
RequestLogwhen anHttpStatusExceptionis raised. #3719 #3674 - You no longer see a
NullPointerExceptionwhen an HTTP/1 connection of a client is closed. #3729 #3747 - You no longer see a
NullPointerExceptionwhen aStreamMessageis aborted. #3731 - No more unintended leakage of sensitive information when logging a
RequestLog. #3758 - A request is now correctly reset when
ServiceRequestContext.cancel()is called. #3674 - You no longer see a
ClassNotFoundExceptionin the shaded fastutil. #3713 - Armeria now accepts harmless yet illegal characters in a request path for HTTP/1 connections, like it did for HTTP/2. #3778
- You no longer see an
EmptyEndpointGroupExceptionwhenEndpointsare completely switched to newEndpointsinHealthCheckedEndpointGroup. #3637 - You can now correctly monitor a
BlockingTaskExecutorwith Micrometer. #3710 #3718 BlockingTaskExecutor's idle threads are now properly terminated after timeout. #3718- You no longer see a
ClassNotFoundExceptionwhen aTransportTypeis initialized. #3717 - The second
RetryRuleWithContentis now properly applied to decide to retry or not when usingRetryingClient. #3720 requestDurationNanos,responseDurationNanosandtotalDurationNanosof a child log are properly calculated. #3749- An
IllegalArgumentExceptionraised in annotated service will be correctly logged byLoggingService. #3674 - You no longer see
ClosedSessionExceptionwhen a large content is sent to an invalid path. #3674 ServerBuilder.localPort()correctly binds both IPv4 and IPv6 loopback addresses. #3725 #3726- A
Publisheror aCompletableFuturereturned by a service now properly gets a cancellation signal when the request is cancelled. #3690 #3691 - Brave's
ScopeDecorators now correctly propagates a decoratedScopecontext when usingBraveServiceandBraveClient. #3408 #3430 - A
Serverin Spring integration is now automatically started bySmartLifecycle. #3759 #3762 - You no longer see
CancelledSubscriptionExceptionwhen a WebFlux'sWebClientreceives an unexpectedContent-Typeheader. #3730 #3750 TomcatServicenow properly handles an exception with Spring Web'sExceptionHandlers. #3447 #3732ErrorWebExceptionHandlerin WebFlux converts an exception to the response correctly. #3779JettyServicedoesn't raise anEofExceptionanymore. #3688- Kotlin
jsr305=strictoption no longer raises a false positive error when a null value is returned from where nulls are allowed to return. #3751 - You no longer see null values when a case class is decoded from a JSON when using annotated services with Scala. 400 Bad Request will be returned instead. #3728
🏚️ Deprecations
-Dcom.linecorp.armeria.annotatedServiceExceptionVerbosityandExceptionVerbosityhas been deprecated. #3674- You can use
LoggingServicethat automatically logs exceptions using for your convenience; or manually log exceptions usingServerBuilder.exceptionHandler().
- You can use
ObservableResponseConverterFunction.<init>(ResponseConverterFunction,ExceptionHandlerFunction)has been deprecated in favor ofObservableResponseConverterFunction.<init>(ResponseConverterFunction). #3674HttpResponseException.of(HttpStatus)andHttpResponseException.of(int)has been deprecated in favor ofHttpStatusException. #3674
☢️ Breaking changes
ExceptionHandlernow returnsHttpResponseinstead ofAggregatedHttpResponse. #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; }RequestContextis added as the first parameter ofGrpcStatusFunction. #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; });JacksonModuleProviderhas been removed in favor ofJacksonObjectMapperProvider.ResponseConverterFunctionProvider.createResponseConverterFunction(Type,ResponseConverterFunction,ExceptionHandlerFunction)has been removed in favor ofResponseConverterFunctionProvider.createResponseConverterFunction()?full. #3674OAuth2AuthorizationGrant.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