Skip to main content

v1.41.0

August 7, 2026

🌟 New features

  • Server-Side xDS: An Armeria server can now be configured by an xDS control plane using XdsServerPlugin, which manages TLS certificates and HTTP filter decoration on the given ports. Virtual hosts, routes and services stay user-defined — xDS never takes over service dispatch. See the new xDS documentation. #6820 #6833 #6837
    Server.builder()
    .plugin(XdsServerPlugin.of(xdsBootstrap, "listener_name", 8080)) // 👈👈👈
    .service("/api", myService)
    .build();
    • A Listener's filter_chains and default_filter_chain are fully resolved, so TLS and routing can be configured per filter chain.
    • A connection is matched by destination port, SNI hostname, transport type and ALPN, and its certificate is selected by SNI — exact DNS SAN match, then wildcard, then the first.
  • Server Plugin: Bundle server-level concerns — ports, TLS, decorators, listeners — into a reusable ServerPlugin registered with ServerBuilder.plugin(). Plugins are re-installed on Server.reconfigure() and closed on stop. #6825
    public final class ObservabilityPlugin implements ServerPlugin {

    @Override
    public void install(ServerBuilder sb) { // 👈👈👈
    sb.decorator(MetricCollectingService.newDecorator(
    MeterIdPrefixFunction.ofDefault("http.service")))
    .decorator(LoggingService.newDecorator())
    .service("/internal/healthcheck", HealthCheckService.of());
    }
    }
  • Per-Request Client TLS and SNI Override: ClientTlsProvider resolves a ClientTlsSpec per request from the ClientRequestContext, and the SNI hostname is now readable via ClientRequestContext.sniHostname() and overridable via ClientRequestContext.setSniHostname(). #6855
    ClientFactory.builder()
    .tlsProvider((ClientTlsProvider) ctx -> // 👈👈👈
    ClientTlsSpec.builder()
    .trustedCertificates(certsFor(ctx.sniHostname()))
    .build())
    .build();
  • Independent TLS for CONNECT Proxies: ProxyConfig.connect() configures the proxy hop's TLS separately from the backend — a different trust store, proxy mTLS certificate or ALPN list. An unset ALPN list defaults to HTTP/1.1, since that is what CONNECT proxies speak. #6854
  • Connection-Aware Server TLS Provider: ServerTlsProvider resolves a ServerTlsSpec from a ConnectionContext, so a certificate can be chosen by SNI hostname, ALPN, remote address or custom attributes — not the hostname alone. Returning null falls back to the virtual host's TLS settings. #6806
  • Circuit Breaker Alignment with SuccessFunction: CircuitBreakerRuleBuilder.onSuccessFunction() and its CircuitBreakerRuleWithContentBuilder.onSuccessFunction() counterpart make a rule match when the client's SuccessFunction regards a response as a success. Opting in also fixes a breaker that could otherwise never close because its trial requests kept matching thenIgnore(). #6828
    CircuitBreakerRule rule =
    CircuitBreakerRule.builder()
    .onSuccessFunction() // 👈👈👈
    .thenSuccess()
    .orElse(myRule);
  • Custom JSON Marshaller for Transcoding Proxies: DelegatingHttpJsonTranscodingServiceBuilder.jsonMarshallerFactory() lets a standalone HTTP/JSON transcoding proxy use a custom GrpcJsonMarshaller, for example to render proto3 fields that hold their default value. #6826
  • HTTP QUERY Method: Armeria now supports QUERY from RFC 10008, a safe, idempotent method that carries a request body. WebClient, BlockingWebClient and RestClient can send it, and a server handles it with @Query or AbstractHttpService.doQuery(). #6861
    WebClient client = WebClient.of("https://example.com");
    HttpResponse res = client.query("/search", "q=armeria"); // 👈👈👈 the body carries the query

    // On an annotated service
    @Query("/search") // 👈👈👈
    public HttpResponse search(HttpRequest req) { ... }
  • Athenz xDS Filters: The new xds-athenz module lets an xDS control plane drive Athenz authentication and authorization — an outbound filter injects access tokens, and an inbound filter authorizes requests against Athenz policies with configurable action/resource mapping. #6853
  • Envoy Fault Injection: The envoy.filters.http.fault filter is now supported, injecting abort responses and delays into the request path on both the client and the server side, optionally only when request headers match. #6888
  • Weighted Clusters: weighted_clusters in a RouteAction splits traffic across backend clusters by weight — say 90% stable, 10% canary. Each target still honors its own typed_per_filter_config and metadata_match. #6836

📈 Improvements

  • A failure to bind a port at startup is now wrapped in ServerPortBindException, whose ServerPortBindException.serverPort() reports exactly which ServerPort failed, so probing ports before starting the server is no longer necessary. The original transport exception is kept as the cause, and non-bind startup failures are propagated unchanged. #6819
    if (Exceptions.peel(e) instanceof ServerPortBindException bindEx) {
    ServerPort port = bindEx.serverPort(); // 👈👈👈 which port failed, and why
    }
  • TlsKeyPair now validates that the private key matches the leaf certificate's public key at construction time, so a mismatch fails fast. #6892
  • ClientRequestContext.setClientTlsSpec() and the new ClientRequestContext.clearClientTlsSpec() let a decorator turn TLS on or off for an individual request. #6895
  • ZtsBaseClient is now an interface, so you can supply your own WebClient — for example one built from an xDS cluster snapshot. JWKS keys are fetched with that same WebClient, honoring its TLS and proxy configuration, instead of a separate SSLContext. ZtsBaseClient.builder() usage is unchanged. #6848
  • xDS — the following improve client behavior and custom extensions:
    • A cluster's HttpProtocolOptions is now honored, so a cluster that explicitly declares HTTP/1 or HTTP/2 gets the matching SessionProtocol instead of defaulting to HTTP/HTTPS. #6843
    • Load balancer selection is observable through the armeria.xds.lb.select and armeria.xds.lb.select.subset counters, tagged with cluster, priority, region, zone, sub.zone and result=hit/result=miss. #6840
    • A custom cluster type can be plugged in by registering an XdsExtensionFactoryProvider that supplies a ClusterTypeFactory, then naming it in the Cluster's cluster_type. #6811 #6838
    • RetryBackOff accepts an exponential_backoff_factor, so the retry multiplier is no longer fixed at Envoy's 2x. This is an Armeria-specific extension, absent from upstream Envoy. #6874
    • A custom config source only needs to return a SnapshotStream of DiscoveryResponse from SotwConfigSourceSubscriptionFactory; Armeria handles parsing, storage and notification. #6795
    • An HttpFilter with disabled: true is now skipped, and RPC (Thrift) clients honor the xDS RetryPolicy, which was previously ignored silently. #6864
    • XdsPreprocessor.whenReady() waits for the first snapshot, so a client does not race the initial configuration fetch. #6838
    • Custom protobuf packages can be registered for XdsResourceReader via the XdsTypeRegistryPackageProvider SPI, and custom_config_source also resolves by name. #6838
    • SnapshotStream.caching() deduplicates keyed subscriptions by reference counting, so external modules no longer need to fork the internal implementation. #6831

🛠️ Bug fixes

  • Closing a DnsAddressEndpointGroup now releases the UDP socket held by its underlying DNS resolver. Previously every group leaked one socket for the lifetime of the process. #6846
  • Resetting an HTTP/1 connection while pipelined requests are in flight no longer fails with a NullPointerException. The pending responses are now failed with a ClosedSessionException, as intended. #6899
  • TlsKeyPair.ofSelfSigned() no longer fails on a machine whose hostname is longer than 64 characters, which used to break certificate generation with commonName length ... exceeds RFC 5280 ub-common-name (64). The hostname is truncated for the certificate's common name. #6900
  • A request that arrives during the brief window of Server.reconfigure() is now served normally by AuthService, instead of failing with a NullPointerException and 500 Internal Server Error. #6832
  • An authority that combines a long hostname with a port no longer throws IllegalArgumentException: The label in the input is too long. IDN normalization is now applied to the parsed hostname rather than to the whole authority. #6859 #6860
  • A certificate with neither a common name nor a subject alternative name — typically a root CA found in a trust store — now exports tls.certificate.* metrics tagged with its subject DN instead of an empty hostname. #6862
  • AttributesSetters.getAndSet() no longer throws a ClassCastException when the attribute had been set to null to hide a value inherited from a parent. It is now declared @Nullable and returns null in that case instead of an internal sentinel. #6889
  • A failure to obtain an Athenz token now surfaces its original cause, such as UnprocessedRequestException, instead of a NullPointerException or a misleading AccessDeniedException. Only a 403 Forbidden from ZTS is wrapped now, so code that caught AccessDeniedException for a 401 or 400 sees the original exception instead. #6866
  • Three xDS defects are fixed: server-side filter chain matching now rejects a connection whose protocol does not match the matched filter chain's TLS configuration; the lb.zar.local.percentage gauge reports 1 rather than 100 when routing is fully local, so dashboards built on it are no longer 100x off; and a watcher no longer receives a spurious MissingXdsResourceException when a cached resource already exists at registration time. #6887

📃 Documentation

  • The xDS module is now documented on the site, covering concepts, bootstrap configuration, client and server usage, extensions, metrics and supported features. #6724

🏚️ Deprecations

☢️ Breaking changes

⛓ Dependencies

  • Athenz 1.12.42 → 1.12.44
  • GraphQL Java 25.0 → 26.0
  • gRPC-Java 1.81.0 → 1.83.0
  • gRPC-Kotlin 1.4.1 → 1.4.3
  • Jackson 2.22.0 → 2.22.1
  • java-jwt 4.5.2 → 4.6.0
  • Javassist 3.31.0 → 3.32.0
  • JBoss Logging Annotations 2.2.1 → 2.2.2
  • Jetty 9.4.55 → 9.4.58, 12.0.32 → 12.0.37
  • JUnit 5.14.2 → 5.14.4
  • Kafka 3.9.1 → 3.9.2
  • Kotlin 2.4.0 → 2.4.10
  • MCP 1.1.3 → 2.0.0
  • Netty 4.2.15 → 4.2.16
  • Prometheus 1.7.0 → 1.8.0
  • Protobuf 3.25.8 → 3.25.9
  • RESTEasy 5.0.9 → 5.0.10
  • Sangria 4.2.18 → 4.2.19
  • Spring 6.2.15 → 6.2.19
  • Spring Boot 3.5.10 → 3.5.16, 4.0.6 → 4.1.0
  • Tomcat 9.0.96 → 9.0.120, 10.1.49 → 10.1.57

🙇 Thank you

This release was possible thanks to the following contributors who shared their brilliant ideas and awesome pull requests:

@davinkevin@minwoox@desiderantes@Gautam-aman@0x1306e6d@habara-k@jrhee17@trustin@clketa@xuukaka2023-art@ikhoon

Like Armeria?
Star us ⭐️

×