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 #6837Server.builder().plugin(XdsServerPlugin.of(xdsBootstrap, "listener_name", 8080)) // 👈👈👈.service("/api", myService).build();- A
Listener'sfilter_chainsanddefault_filter_chainare 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.
- A
- Server Plugin: Bundle server-level concerns — ports, TLS, decorators, listeners — into a
reusable
ServerPluginregistered withServerBuilder.plugin(). Plugins are re-installed onServer.reconfigure()and closed on stop. #6825public final class ObservabilityPlugin implements ServerPlugin {@Overridepublic 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:
ClientTlsProviderresolves aClientTlsSpecper request from theClientRequestContext, and the SNI hostname is now readable viaClientRequestContext.sniHostname()and overridable viaClientRequestContext.setSniHostname(). #6855ClientFactory.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:
ServerTlsProviderresolves aServerTlsSpecfrom aConnectionContext, so a certificate can be chosen by SNI hostname, ALPN, remote address or custom attributes — not the hostname alone. Returningnullfalls back to the virtual host's TLS settings. #6806 - Circuit Breaker Alignment with
SuccessFunction:CircuitBreakerRuleBuilder.onSuccessFunction()and itsCircuitBreakerRuleWithContentBuilder.onSuccessFunction()counterpart make a rule match when the client'sSuccessFunctionregards a response as a success. Opting in also fixes a breaker that could otherwise never close because its trial requests kept matchingthenIgnore(). #6828CircuitBreakerRule rule =CircuitBreakerRule.builder().onSuccessFunction() // 👈👈👈.thenSuccess().orElse(myRule); - Custom JSON Marshaller for Transcoding Proxies:
DelegatingHttpJsonTranscodingServiceBuilder.jsonMarshallerFactory()lets a standalone HTTP/JSON transcoding proxy use a customGrpcJsonMarshaller, for example to render proto3 fields that hold their default value. #6826 - HTTP QUERY Method: Armeria now supports
QUERYfrom RFC 10008, a safe, idempotent method that carries a request body.WebClient,BlockingWebClientandRestClientcan send it, and a server handles it with@QueryorAbstractHttpService.doQuery(). #6861WebClient 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-athenzmodule 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.faultfilter 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_clustersin aRouteActionsplits traffic across backend clusters by weight — say 90% stable, 10% canary. Each target still honors its owntyped_per_filter_configandmetadata_match. #6836
📈 Improvements
- A failure to bind a port at startup is now wrapped in
ServerPortBindException, whoseServerPortBindException.serverPort()reports exactly whichServerPortfailed, 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. #6819if (Exceptions.peel(e) instanceof ServerPortBindException bindEx) {ServerPort port = bindEx.serverPort(); // 👈👈👈 which port failed, and why} TlsKeyPairnow validates that the private key matches the leaf certificate's public key at construction time, so a mismatch fails fast. #6892ClientRequestContext.setClientTlsSpec()and the newClientRequestContext.clearClientTlsSpec()let a decorator turn TLS on or off for an individual request. #6895ZtsBaseClientis now an interface, so you can supply your ownWebClient— for example one built from an xDS cluster snapshot. JWKS keys are fetched with that sameWebClient, honoring its TLS and proxy configuration, instead of a separateSSLContext.ZtsBaseClient.builder()usage is unchanged. #6848- xDS — the following improve client behavior and custom extensions:
- A cluster's
HttpProtocolOptionsis now honored, so a cluster that explicitly declares HTTP/1 or HTTP/2 gets the matchingSessionProtocolinstead of defaulting toHTTP/HTTPS. #6843 - Load balancer selection is observable through the
armeria.xds.lb.selectandarmeria.xds.lb.select.subsetcounters, tagged withcluster,priority,region,zone,sub.zoneandresult=hit/result=miss. #6840 - A custom cluster type can be plugged in by registering an
XdsExtensionFactoryProviderthat supplies aClusterTypeFactory, then naming it in the Cluster'scluster_type. #6811 #6838 RetryBackOffaccepts anexponential_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
SnapshotStreamofDiscoveryResponsefromSotwConfigSourceSubscriptionFactory; Armeria handles parsing, storage and notification. #6795 - An
HttpFilterwithdisabled: trueis now skipped, and RPC (Thrift) clients honor the xDSRetryPolicy, 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
XdsResourceReadervia theXdsTypeRegistryPackageProviderSPI, andcustom_config_sourcealso resolves by name. #6838 SnapshotStream.caching()deduplicates keyed subscriptions by reference counting, so external modules no longer need to fork the internal implementation. #6831
- A cluster's
🛠️ Bug fixes
- Closing a
DnsAddressEndpointGroupnow 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 aClosedSessionException, as intended. #6899 TlsKeyPair.ofSelfSigned()no longer fails on a machine whose hostname is longer than 64 characters, which used to break certificate generation withcommonName 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 byAuthService, instead of failing with aNullPointerExceptionand500 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 emptyhostname. #6862 AttributesSetters.getAndSet()no longer throws aClassCastExceptionwhen the attribute had been set tonullto hide a value inherited from a parent. It is now declared@Nullableand returnsnullin 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 aNullPointerExceptionor a misleadingAccessDeniedException. Only a403 Forbiddenfrom ZTS is wrapped now, so code that caughtAccessDeniedExceptionfor a401or400sees 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.percentagegauge reports1rather than100when routing is fully local, so dashboards built on it are no longer 100x off; and a watcher no longer receives a spuriousMissingXdsResourceExceptionwhen 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
ZtsBaseClient.ztsUri()andZtsBaseClient.proxyUri()are deprecated because they leak implementation details. Read the URI fromZtsBaseClient.webClient()instead ofztsUri();proxyUri()has no replacement, as the proxy is configured on the builder and is already applied to theWebClient. #6848
☢️ Breaking changes
- The SNI hostname is now precomputed when the
Endpointis set, not derived from the authority at connection time. A decorator that rewrites the authority orHostheader no longer changes it — callClientRequestContext.setSniHostname()instead. #6855 ProxyConfig.connect()now gives the proxy hop a defaultClientTlsSpecwith system defaults instead of reusing the client factory's TLS configuration. Pass an explicit spec toProxyConfig.connect()to restore the old behavior. #6854ClientRequestContext.setClientTlsSpec()now switches theSessionProtocolto its TLS variant; callClientRequestContext.clearClientTlsSpec()for cleartext.RequestOptions.clientTlsSpec()only sets a fallback spec and does not switch the protocol. #6895- The
:ai:mcpmodule builds against MCP Java SDK 2.x.ArmeriaStatelessServerTransportandArmeriaStreamableServerTransportProviderkeep their API, but you must move your MCP SDK dependency to 2.0.0, which carries its own breaking API changes. #6889 - xDS — the following affect custom extensions and control-plane configuration:
- User decorators now run before xDS filters (
user decorators → xDS → transport). A decorator that relied on running after them — to read injected headers or wrap xDS retries — must move into an xDS filter. #6890 - A
Clusterof type EDS must seteds_cluster_config; the inlineload_assignmentfallback is gone. #6811 - Unsupported fields are rejected rather than warned about, and validation now covers every proto
package except
google.protobuf— anything outsideenvoy./xds./armeria.used to be skipped. RegisterWarningXdsValidatorIndexvia SPI to keep warning instead of failing. #6853 #6864 XdsHttpFilterdropshttpPreprocessor()/rpcPreprocessor()— implementhttpDecorator()/rpcDecorator()instead. #6864- A single
XdsExtensionFactoryProviderSPI replaces theHttpFilterFactory,SotwConfigSourceSubscriptionFactoryandClusterTypeFactoryregistrations; move yourMETA-INF/servicesentry to it. Built-ins are no longer SPI-overridable — register custom factories withXdsBootstrapBuilder.extensionFactories(). #6820 #6838 SotwConfigSourceSubscriptionFactory.create()returns aSnapshotStreamofDiscoveryResponse;ResourceWatcher,XdsStreamSubscriber,ConfigSourceSubscriptionandSotwSubscriptionCallbacksare removed. #6795
- User decorators now run before xDS filters (
⛓ 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:










