1.14.0 release notes
27th January 2022
🌟 New features
You can now use Scala 3 for
armeria-scalaandarmeria-scalapb. #3614 #4036You can now fluently convert an
HttpResponseinto a desired type usingWebClient. #4021WebClient 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
BlockingWebClientto wait for anHttpResponseto be completed. #4021BlockingWebClient 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 #3975GrpcClients .builder(...) .decorator(myDecorators) .maxResponseMessageLength(MAX_MESSAGE_SIZE) .jsonMarshallerFactory(descriptor -> { ... }) .intercept(myInterceptors) .build(MyStub.class);You can now fluently build an
HttpResponseby usingHttpResponseBuilder. #3398 #3941HttpResponse .builder() .ok() .header(HttpHeaderNames.USER_AGENT, "Armeria") .content(content) .build();You can now use
StreamMessage.mapAsync()to transform aStreamMessageusing an async operation. #3916 #3962StreamMessage<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()andStreamMessage.peekError()operators onHttpRequestandHttpResponse. #3097 #3988 #3949HttpRequest 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
ServerErrorHandlerdepending on theRequestHeaders. #4037You can now use
{*var}and:*varpath patterns to capture rest paths of a request. #3031 #3997Server .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 #3960You can now send query parameters using
QueryParamswithWebClient. #3915 #3921QueryParams 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 usingStreamMessages.writeTo(). #3870 #3874StreamMessage<HttpData> stream = ...; Path destination = ...; StreamMessages.writeTo(stream, destination);You can now force a state transition of a
CircuitBreakerby usingCircuitBreaker.enterState(). #4010 #4022CircuitBreaker circuitBreaker = CircuitBreaker.of(...); circuitBreaker.enterState(CircuitState.FORCED_OPEN);You can now conveniently create a
RetryRuleandRetryRuleWithContentby specifying aBackoff. #3875 #2899 #3895RetryRule.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 #3985ConcurrencyLimitingClient.newDecorator(new DynamicLimit()); class DynamicLimit implements IntSupplier { @Override public int getAsInt() { // Dynamically returns the current limitation. ... } }You can now split an
HttpRequestintoRequestHeaders,StreamMessage<HttpData>and trailers usingHttpRequest.split(). #3924 #3953HttpRequest 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. #4040You can now customize the
MediaTypeof a file when usingFileService. #4001 #4009FileService .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()orServerRule.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 #3928You can now use
UnframedGrpcStatusMappingFunctionto customize how a gRPC status is mapped to anHttpStatuswhen using an unframed gRPC service. #3683 #3948UnframedGrpcStatusMappingFunction 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 #3946armeria: ... internal-services: # Actuator will be exposed to 18080 port. include: actuator, health port: 18080Added
JettyServiceBuilder.tlsReverseDnsLookup()that makesJettyServiceperform a reverse DNS lookup for each TLS connection, which may be useful for servlets that assumeServletRequest.getRemoteHost()will return a remote host name rather than a remote IP address.Added
JettyServiceBuilder.httpConfiguration()that allows a user to set JettyHttpConfigurationin a type-safe manner.
📈 Improvements
- You can now distinguish services, messages and enums with the same name by their package names from
DocServiceUI. #3979 - Improved the memory footprint of
PrometheusExpositionServiceby removing large byte array allocations. #3929 - Meters created by
MoreMetersare now using more accurate percentiles. #4047
🛠️ Bug fixes
- You no longer see
ClassCastExceptionwhen duplicating anHttpRequestor anHttpResponse. #4030 #4032 - You no longer see
IllegalArgumentExceptionif an upper-bounded wildcard type with@Paramis used in annotated services. #4007 #4008 - You can now properly get
SameSiteattribute fromCookie.sameSite(). #3968 #3984 - You no longer see an
EmptyEndpointGroupExceptionwhen anEndpointGroupis successfully resolved with the initialEndpoints. #3978 HttpRequestBuilder.header()does not overwrite an old header value anymore. #3932 #3941- A new header value will be appended to the old value.
- You no longer see
413 Request Entity Too Largewhen a server receives a cleartext (h2c) upgrade request larger than 16384 bytes. #3859 #3913- The maximum allowed length of an upgrade request will respect
ServerBuilder.maxRequestLength()orVirtualHost.maxRequestLength().
- The maximum allowed length of an upgrade request will respect
- Fixed
Multipartdecoder to correctly publish chunks one by one when streaming a largeBodyPart. #3774 #3783 - You can now use
google.protobuf.{Struct, Value, ListValue, Any}messages from yourprotofiles when using gRPC-JSON transcoder. #3986 #3992 GrpcServicenow automatically disables gRPC-JSON serialization for Protobuf 2 services rather than throwing an exception. #4020 #4033FileServicenow keeps query parameters when redirecting to a directory. #4049 #4050- You no longer see
InvocationTargetExceptionwith a Kotlin method whose return type isNothingin an annotated service. #3961 #4005 - Actuator now correctly shows
LivenessandReadinessof a Kubernetes instance. #3031 #3997
🏚️ Deprecations
CircuitBreaker.canRequest()has been deprecated in favor ofCircuitBreaker.tryRequest(). #4012Cookie.builder()andCookie.of()have been deprecated in favor ofCookie.secureBuilder()andCookie.ofSecure(). #3788 #3939GrpcServiceBuilder.setMaxInboundMessageSizeBytes()andGrpcServiceBuilder.setMaxOutboundMessageSizeBytes()have been deprecated in favor ofGrpcServiceBuilder.maxRequestMessageLength()andGrpcServiceBuilder.maxResponseMessageLength(). #3999AbstractBlockingHttpVfs.get(Executor,String,Clock,String,HttpHeaders)has been deprecated in favor ofAbstractBlockingHttpVfs.get(Executor,String,Clock,String,HttpHeaders,MediaTypeResolver). #4009HttpVfs.get(Executor,String,Clock,String,HttpHeaders)has been deprecated in favor ofHttpVfs.get(Executor,String,Clock,String,HttpHeaders,MediaTypeResolver). #4009
☢️ Breaking changes
HealthChecker.of(Supplier,Duration)andHealthChecker.of(Supplier,Duration,EventExecutor)now returnsListenableHealthCheckerinstead ofHealthChecker. #4017ServerErrorHandler.renderStatus()now requires an additional parameterRequestHeaders. #4037- This allows a user to generate a different error response depending on the request header value.
⛓ 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