0.98.0 release notes
12th February 2020
🎬 Before we begin
This release contains more breaking changes and deprecations than usual, which were necessary for the preparation of 1.0 release and the long term evolution of our API after 1.0. We'd like to apologize for any inconveniences caused by the breaking changes. Please don't forget we're always here for you and do let us know if you have any trouble upgrading, so we can help you!
🌟 New features
- Since this release, in preparation of 1.0 release, we annotated classes and packages which may have a chance of breaking change with the @UnstableApiannotation. #2445
- The attribute access API of RequestContexthas been revamped for simplicity. #2322AttributeKey<String> MY_ATTR = AttributeKey.valueOf("MY_ATTR"); RequestContext ctx = ...; ctx.setAttr(MY_ATTR, "foo"); assert "foo".equals(ctx.attr(MY_ATTR));
- When a RequestContextis derived or inherited from anotherRequestContext, the parent context's attributes are now visible from the derived or inherited context: #2322AttributeKey<String> MY_ATTR = AttributeKey.valueOf("MY_ATTR"); ServiceRequestContext parentCtx = ...; ClientRequestContext childCtx = ...; assert childCtx.root() == parentCtx; parentCtx.setAttr(MY_ATTR, "foo"); // Root context's attributes are visible from its children. assert "foo".equals(childCtx.attr(MY_ATTR)); // Not visible anymore if the attribute is overwritten. childCtx.setAttr(MY_ATTR, "bar"); assert "foo".equals(parentCtx.attr(MY_ATTR)); assert "bar".equals(childCtx.attr(MY_ATTR));
- {Client,Service}RequestContextnow provides more useful ways to schedule request or response timeout: #2343- Server.builder() .service("/svc", myService.decorate((delegate, ctx, req) -> { if (req.headers().contains("x-extend-timeout")) { // Extend the timeout by 10 seconds. ctx.extendRequestTimeout(Duration.ofSeconds(10)); } return delegate.serve(ctx, req); }));
- RequestLogAPI has been revamped for safety and usability. #2342- RequestContext ctx = ...; // Asynchronous retrieval: ctx.log().whenRequestComplete().thenAccept(log -> { // Can't access response properties at compilation level. assert log instanceof RequestOnlyLog; System.err.println(log.toStringRequestOnly()); }) ctx.log().whenComplete().thenAccept(log -> { // Can access all properties. assert log instanceof RequestLog; System.err.println(log.toStringResponseOnly()); }); ctx.log().whenAvailable(RequestLogProperty.SESSION).thenAccept(log -> { // Advanced use case: // Raises an exception if accessing an unavailable property. System.err.println(log.channel()); }); // Accessing a property ensuring availability: ctx.log().ensureRequestComplete().requestEndTimeNanos(); ctx.log().ensureComplete().responseEndTimeNanos(); ctx.log().ensureAvailable(RequestLogProperty.RESPONSE_CONTENT) .responseContent();
- RequestLogalso has a new property- NAME, which can be used as a method name in an RPC call, a span name in distributed tracing or any other human-readable string that can be used for identifying a request. #2413
- Added a new immutable API for encoding and decoding HTTP query strings: #2307QueryParams params = QueryParams.builder() .add("foo", "1") .add("bar", "2") .build(); // Encoding String queryString = params.toQueryString(); assert "foo=1&bar=2".equals(queryString); // Decoding QueryParams decodedParams = QueryParams.fromQueryString("foo=1&bar=2"); assert decodedParams.equals(params); // Mutation QueryParams newParams = params.toBuilder() .add("baz", "3") .build(); assert "foo=1&bar=2&baz=3".equals(newParams.toQueryString());
- Added various convenient boolean getter methods to HttpStatus: #2435assert HttpStatus.CONTINUE.isInformational(); assert HttpStatus.OK.isSuccess(); assert HttpStatus.FOUND.isRedirect(); assert HttpStatus.BAD_REQUEST.isClientError(); assert HttpStatus.SERVICE_UNAVAILABLE.isServerError(); // No need to write like this anymore assert HttpStatus.OK.codeClass() == HttpStatusClass.SUCCESS;
- Added MediaTypeNameswhich providesStringversion of well knownMediaTypes, which is useful when writing an annotated service: #2438class MyAnnotatedService { @Get("/download/zip") @Produces(MediaTypeNames.ZIP) HttpResponse downloadArchive() { ... } }
- You can now add {Request,Response}ConverterFunctionandExceptionHandlerFunctionto all annotated services in yourServereasily. #2316Server.builder() .annotatedService("/users", userService) .annotatedService("/posts", postService) .annotatedService("/files", fileService) // Applies all extensions to all 3 annotated services. .annotatedServiceExtensions( commonRequestConverters, commonResponseConverters, commonExceptionHandlers) .build();
- You can now require a route to have HTTP headers and/or query parameters: #2102Server.builder() // Route to 'myService' only when: // - 'x-must-exist' header exists, // - and 'bar' query parameter exists. .route().get("/foo") .matchesHeaders("x-must-exist") .matchesParams("bar") .build(myService)
- You can now customize the SslContextcreated fromServerBuilder.tlsSelfSigned()orVirtualHost.tlsSelfSigned(): #2340Server.builder() .tlsSelfSigned() .tlsCustomizer(sslCtxBuilder -> { sslCtxBuilder.ciphers(...); })
- You can now close an EndpointGroupasynchronously: #2430DnsAddressEndpointGroup group = DnsAddressEndpointGroup.of("cluster.com", 8080); group.whenClosed().thenRun(() -> { System.err.println("Closed!"); }); group.closeAsync();
- You do not need to register your EndpointGrouptoEndpointGroupRegistryfor client-side load balancing. Just specify it when you build a client: #2381EndpointGroup group = EndpointGroup.of( Endpoint.of("node1.cluster.com"), Endpoint.of("node2.cluster.com")); // Thrift HelloService.Iface client = Clients.builder("tbinary+http", group) .path("/api/thrift/hello") .build(HelloService.Iface.class); // gRPC HelloServiceBlockingStub client = Clients.builder("gproto+http", group) .build(HelloServiceBlockingStub.class); // Web WebClient client = WebClient.of(SessionProtocol.HTTP, group);
- You can now limit the number of endpoints in HealthCheckedEndpointGroup, which is very useful when there are many candidate endpoints in the group but you want to send requests to only a few of them, to avoid unnecessarily large number of outbound connections: #2177HealthCheckedEndpointGroup group = HealthCheckedEndpointGroup.builder(delegateGroup, "/health") .maxEndpointCount(3) .build();
- You can now capture the ClientRequestContextof your client call withClientRequestContextCaptor: #2344WebClient client = WebClient.of("http://foo.com/"); try (ClientRequestContextCaptor ctxCaptor = Clients.newContextCaptor()) { HttpResponse res = client.get("/"); ClientRequestContext ctx = ctxCaptor.get(); ... }
- Added ClientFactory.insecure()andClientFactoryBuilder.tlsNoVerify()to simplify testing SSL/TLS connections with self-signed certificates: #2340// Using the default insecure factory WebClient.builder("https://127.0.0.1:8443") .factory(ClientFactory.insecure()) .build(); // Using a custom insecure factory WebClient.builder("https://127.0.0.1:8443") .factory(ClientFactory.builder() .tlsNoVerify() ... .build()) .build();
- ClientFactoryis now part of- ClientOptionsfor easier creation of derived clients. #2384- ClientFactory factory = ...; WebClient client = WebClient.builder(...) .factory(factory) .build(); WebClient clientCopy = WebClient.builder(...) .options(client.options()) .build(); // Note that ClientFactory is copied as well. assert client.factory() == clientCopy.factory();
- RetrofitMeterIdPrefixFunctionis now capable of adding HTTP method and request path pattern if you specify a Retrofit service class: #2356
- New module armeria-dropwizardprovides the integration with Dropwizard, which allows you to leverage the best of the both worlds. #2236- See Using Armeria with Dropwizard for more information.
- Special thanks to @cricket007 who volunteered for this.
 
- You can now customize DocServicewhen integrating with Spring framework by injectingDocServiceConfigurator: #2327@Bean public DocServiceConfigurator docServiceConfigurator() { // Exclude all Thrift services from DocService. return docServiceBuilder -> { docServiceBuilder.exclude(DocServiceFilter.ofThrift()); }; }
- ServerRule(JUnit 4) and- ServerExtension(JUnit 5) now have more getters: #2449- Endpointgetters:- endpoint(SessionProtocol),- httpEndpoint()and- httpsEndpoint()
 
- URIgetters:- uri(SessionProtocol),- uri(SessionProtocol, SerializationFormat),- httpUri(),- httpUri(SerializationFormat),- httpsUri()and- httpsUri(SerializationFormat)
- The old deprecated getters return Stringinstead ofURI.
 
- InetSocketAddressgetters:- socketAddress(SessionProtocol)
 
 
- The CompletableFutures returned by our API will leave a warning log like the following when you perform a blocking operation in an event loop thread: #2275You can disable this functionality by specifying theCalling a blocking method on CompletableFuture from an event loop or non-blocking thread. You should never do this ... java.lang.IllegalStateException: Blocking event loop, don't do this.-Dcom.linecorp.armeria.reportBlockedEventLoop=falseJVM option.
- You can now serialize and deserialize ThriftCall,ThriftReply,TMessageandTBaseinto TTEXT JSON usingThriftJacksonModule. #2439ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new ThriftJacksonModule());
- You can now make any SLF4J Loggercontext-aware withRequestContext.makeContextAware(Logger): #2341// Prints 'Hello!' Logger logger = ...; logger.info("Hello!"); // Prints '[<current context>] Hello!' Logger ctxAwareLogger = ctx.makeContextAware(logger); ctxAwareLogger("Hello!");
- RequestContextExporteris now part of the core API, allowing you to integrate with other logging frameworks than Logback, such as Log4J2. #2314
- You can now disable HTTP header validation the the -Dcom.linecorp.armeria.validateHeaders=falseJVM option.
📈 Improvements
- Slightly reduced memory footprint of Logging{Client,Service}#2341
- UnknownHostExceptionraised by Armeria now explains what DNS query has failed. #2332
- WebClientnow accepts a URI that starts with- none+as well. #2361
- HealthCheckedEndpointGroupnow logs a helpful warning message when it receives a 4XX response. #2401
- Our builder API Javadoc does not show mysterious return type parameter such as BandSELFanymore. #2454
- A client now gets SSLExceptioninstead ofClosedSessionExceptionif a connection attempt fails with an SSL/TLS handshake error. #2338
🛠️ Bug fixes
- WebClientdoes not omit a request query string when sending a request to an absolute URI. #2309
- A user cannot start a Serverwith a misconfiguredSslContextanymore. #2325
- A user now always gets the correct RequestContexteven if the contexts are nested. #1083
- Fixed a bug where thread-local context customizers were called for derived contexts unintentionally. #2344
- Clients.withHttpHeaders()and- withContextCustomizer()now work with gRPC calls. #2344
- ClientRequestContext.path()now returns a correct path for gRPC client calls. #2344
- You can now send a POST request with an empty body with DocServiceclient. #2357
- Server-side route cache hit ratio was not as good as we intended. #2358
- Fixed various potential memory leak in HttpResponseWriter. #2359
- Long-polling health check mechanism now detects a stall server which accepts a connection but does not send any response. #2392
- ClientFactoryOptionsdoes not raise a- NullPointerExceptionanymore. #2387
- An AsyncMethodCallbackspecified when making an async Thrift call now has thread-localClientRequestContextset properly. #2383
- gRPC client and server now works well with non-official gRPC stub generators such as reactive-grpc. #2376
- Fixed a bug where a Servercan be started back afterclose()is called. #2406
- Fixed a regression where Reactor does not treat Armeria's event loop threads as non-blocking. #2404
- Armeria does not fail to initialize anymore even if it failed to load the com.linecorp.armeria.versions.propertiesfile. #2398
- You'll not see the cannot start a new stream with a DATA frameerrors anymore. #2429
- RequestLog.requestStartTimeproperty now includes the time taken for making a connection attempt and the time taken by decorators. #2436
- The -Dcom.linecorp.armeria.dumpOpenSslInfo=trueJVM option does not trigger aStackOverflowErroranymore. #2418
- Fixed cosmetic issues in DocServiceclient sidebar. #2470
- Made sure IPv6 DNS queries are not sent to some IPv4 only machines with a link-local IPv6 interface. #2464
🏚️ Deprecations
- HttpParametershas been deprecated in favor of- QueryParams#2307
- ServerBuilder.tls()methods that require a- Consumerhave been deprecated. Use- tlsCustomizer(Consumer)instead. #2340- // Before: Server.builder() .tls(..., sslCtxBuilder -> { ... }); // After: Server.builder() .tls(...) .tlsCustomizer(sslCtxBuilder -> { ... });
- Many classes which have Httpin their names have been deprecated in favor of those withoutHttp, e.g. #2323- RetryingHttpClient->- RetryingClient
- HttpFileService->- FileService
 
- Many builder classes' constructors have been deprecated in favor of builder()static methods, e.g. #1719- new ClientBuilder()->- Clients.builder()
- new ArmeriaRetrofitBuilder()->- ArmeriaRetrofit.builder()
- new ServerCacheControlBuilder()->- ServerCacheControl.builder()
 
- Many public static final fields that are not truly constants have been deprecated in favor of static factory methods, e.g.- EndpointSelectionStrategy.ROUND_ROBIN->- roundRogin()
- NodeValueCodec.DEFAULT->- ofDefault()
- AuthTokenExtractors.BASIC->- basic()
 
- MoreNamingConventionshas been deprecated because we follow Micrometer's recommended naming convention. #2367
- Version.identify()has been deprecated in favor of- getAll()#2398
- ServiceRequestContext.setRequestTimeout*()has been deprecated in favor of- extendRequestTimeout*(),- setRequestTimeoutAfter*(),- setRequestTimeoutAt*()and- clearRequestTimeout(). #2343
- ClientRequestContext.setResponseTimeout*()has been deprecated in favor of- extendResponseTimeout*(),- setResponseTimeoutAfter*(),- setResponseTimeoutAt*()and- clearResponseTimeout(). #2343
- Many methods that return a CompletableFuturehave been renamed from*Future()towhen*(), e.g. #2427- HttpRequest.completionFuture()->- whenComplete()
- HttpResponseWriter.onDemand()->- whenConsumed()
- EndpointGroup.initialEndpointsFuture()->- whenReady()
 
- Many URI-returning methods in ServerRuleandServerExtensionhave been deprecated in favor of the new methods that do not require a path parameter: #2449ServerExtension server = ...; // Before server.httpUri("/"); server.httpUri("/foo"); // After server.httpUri(); server.httpUri().resolve("/foo");
- THttpService.allowedSerializationFormats()has been deprecated in favor of- supportedSerializationFormats()for consistency with- GrpcService. #2453
- Service.decorate(Class)has been deprecated in favor of other- decorate()methods that require a function.
- ClosedPublisherExceptionhas been deprecated in favor of- ClosedStreamException. #2468
☢️ Breaking changes
- Most revamped APIs in this release were changed in a backward-incompatible way:- RequestLog
- Attribute API in RequestContext
 
- Content previewing mechanism has been revamped into decorators. Use ContentPreviewingClientandContentPreviewingService.
- You cannot add callbacks to RequestContextanymore because we found this feature results in poor performance and confusing behavior in many cases. We may want to revisit this feature if there is a valid use case for it.
- {Server,VirtualHost}Builder.tls()methods do not throw an- SSLExceptionanymore.- build()will throw an- IllegalStateExceptioninstead. As a result, any SSL configuration failure will be known when a user calls- build(), rather than- tls().
- We were not able to keep some classes or method signatures while we remove Httpfrom class names.
- ServiceRequestContext.logger()has been removed due to performance issues with Log4J2.
- RequestContext.isTimedOut()has been removed.
- We do not support Tomcat 8.0 anymore, which was obsoleted by Tomcat 8.5 anyway.
- The classes in armeria-grpc-protocolhave been reorganized into multiple packages.
- Our Micrometer meter IDs have been changed, which means you might need to update your monitoring configuration. If you wish to switch back to the legacy naming style, specify the -Dcom.linecorp.armeria.useLegacyMeterNamed=trueJVM option. However, please keep in mind this option will eventually go away, because the new naming convention is recommended by Micrometer.
- All our methods do not return Optionalanymore. They are all@Nullablenow. If you wish to continue usingOptional, just wrap the return value withOptional.ofNullable().
- EndpointGroupRegistryhas been removed, because you can now just specify an- EndpointGroupdirectly when creating a client.- As a result, you need to specify an EndpointSelectionStrategywhen building anEndpointGroup. If unspecified,EndpointSelectionStrategy.weightedRoundRobin()is used.
 
- As a result, you need to specify an 
- MeterIdPrefixFunctionis not a functional interface anymore. You must implement two methods explicitly:- activeRequestPrefix()and- completeRequestPrefix().
- Now that ClientFactoryis a part ofClientOption, the following code will not work as expected, becauseoptions(options)will overwrite the factory.To fix this, you must callClientOptions options = ClientOptions.builder().maxResponseLength(...).build(); ClientFactory factory = ClientFactory.builder().useHttp2Preface(false).build(); WebClient client = WebClient.builder(...) .factory(factory) .options(options) .build(); // This will fail! assert client.options().factory() == factory;options()first, and then override the individual properties:WebClient client = WebClient.builder(...) .options(options) .factory(factory) .build();
- StreamMessage.subscribe(..., boolean withPooledObjects)has been removed. Use- subscribe(..., SubscriptionOption.WITH_POOLED_OBJECTS).
- StreamMessage.drainAll(..., boolean withPooledObjects)has been removed. Use- drainAll(..., SubscriptionOption.WITH_POOLED_OBJECTS).
- HttpRequestDuplicatorand- HttpResponseDuplicatorare now interfaces. Use- HttpRequest.toDuplicator()and- HttpResponse.toDuplicator()to create a duplicator.
- StructuredLogand- StructuredLoggingServicehave been removed. Use- AccessLogWriter.
- ThriftStructuredLogJsonFormathas been removed. Register- ThriftJacksonModuleto- ObjectMapperto serialize or deserialize Thrift objects.
⛓ Dependencies
- Brave 5.9.1 -> 5.9.3
- Dropwizard 1.3.17 -> 1.3.18
- Dropwizard Metrics 4.1.1 -> 4.1.2
- gRPC 1.25 -> 1.27
- Jackson 2.10.1 -> 2.10.2.20200130
- java-jwt 3.8.3 -> 3.9.0
- Jetty 9.4.24 -> 9.4.26
- Micrometer 1.3.2 -> 1.3.3
- Netty 4.1.43 -> 4.1.45- TCNative BoringSSL 2.0.26 -> 2.0.28
 
- Prometheus Java client 0.8.0 -> 0.8.1
- Reactor 3.3.1 -> 3.3.2
- Retrofit 2.6.2 -> 2.7.1
- RxJava 2.2.15 -> 2.2.17
- SLF4J 1.7.29 -> 1.7.30
- Spring Boot 2.2.1 -> 2.2.4, 2.1.10 -> 2.1.12
- Thrift 0.12.0 -> 0.13.0
- Tomcat 9.0.29 -> 9.0.30, 8.5.49 -> 8.5.50