Skip to main content
info

You're seeing the release note of an old version. Check out the latest release note.

v1.33.0

August 6, 2025

🌟 New features

  • Athenz Integration: You can now use the new armeria-athenz module to easily obtain and validate Athenz tokens for secure service-to-service communication. #6050 #6321

    • Server-side validation: Use @RequiresAthenzRole to protect your annotated service endpoints.

      // Prepare a `ZtsBaseClient` to communicate with
      // the Athenz ZTS server.
      ZtsBaseClient ztsBaseClient =
      ZtsBaseClient
      .builder("https://athenz.example.com:4443/zts/v1")
      .keyPair("/var/lib/athenz/service.key.pem",
      "/var/lib/athenz/service.cert.pem")
      .build();

      // Create and register `AthenzServiceDecoratorFactory`.
      final AthenzServiceDecoratorFactory athenzDecoratorFactory =
      AthenzServiceDecoratorFactory
      .builder(ztsBaseClient)
      .policyConfig(new AthenzPolicyConfig("my-domain"))
      .build();
      final DependencyInjector di =
      DependencyInjector.ofSingletons(athenzDecoratorFactory)
      .orElse(DependencyInjector.ofReflective());
      serverBuilder.dependencyInjector(di, true);

      // Decorate methods with `RequiresAthenzRole` to check Athenz role.
      class MyService {
      @RequiresAthenzRole(resource = "user", action = "get")
      @ProducesJson
      @Get("/user")
      public CompletableFuture<User> getUser() {
      ...
      }
      }
      serverBuilder.annotatedService(new MyService());
    • Client-side token management: Automatically cache and attach Athenz tokens to outgoing requests.

      // Decorate the `WebClient` with `AthenzClient` to automatically
      // obtain and attach Athenz tokens.
      WebClient
      .builder()
      .decorator(AthenzClient.newDecorator(ztsBaseClient, "my-domain",
      TokenType.ACCESS_TOKEN))
      ...
      .build();
  • Content Sanitization for Logs: You can now mask sensitive information for AnnotatedService and THttpService using the flexible ContentSanitizer. #6311 #6268

    // For annotated services, use a custom annotation
    // and mark sensitive fields.
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sensitive {}

    class UserRequest {
    private String name;
    @Sensitive // This field will be masked in logs.
    private String phoneNumber;
    ...
    }

    // For Thrift services, set an annotation to the field.
    struct SecretStruct {
    1: string hello;
    2: string secret (sensitive = "");
    }

    // Create `FieldMaskerSelector`s for both types.
    BeanFieldMaskerSelector beanMasker =
    FieldMaskerSelector.ofBean(fieldInfo -> {
    Sensitive sensitive = fieldInfo.getAnnotation(Sensitive.class);
    if (sensitive != null) {
    return FieldMasker.nullify(); // 👈👈👈
    } else {
    return FieldMasker.fallthrough();
    }
    });

    ThriftFieldMaskerSelector thriftMasker =
    ThriftFieldMaskerSelector
    .builder()
    .onFieldAnnotation("sensitive", FieldMasker.nullify()) // 👈👈👈
    .build();

    // Build a `ContentSanitizer` and add it to your `LogFormatter`.
    ContentSanitizer<String> sanitizer =
    ContentSanitizer.builder()
    .fieldMaskerSelector(beanMasker)
    .fieldMaskerSelector(thriftMasker)
    .buildForText();
    LogFormatter formatter = LogFormatter.builderForText()
    .contentSanitizer(contentSanitizer)
    .build();

    // Use the formatter in `LoggingService`.
    ...
  • XDS-based Client Preprocessors: You can now use XdsHttpPreprocessor and XdsRpcPreprocessor to create clients that route requests according to your xDS configuration. #6299

    XdsBootstrap bootstrap = XdsBootstrap.of(...);
    XdsHttpPreprocessor xdsProcessor =
    XdsHttpPreprocessor.ofListener("my-listener", bootstrap);
    WebClient client = WebClient.of(xdsProcessor); // 👈👈👈
    // This request is routed based on the 'my-listener' configuration.
    client.get("/api/v1/resource");
  • Preprocessor-based Clients: It is now possible to create a client solely from a Preprocessor, which allows for dynamic, per-request configuration of the protocol and endpoint. #6060

    HttpPreprocessor preprocessor = (delegate, ctx, req) -> {
    // Dynamically set the session protocol and endpoint group.
    ctx.setSessionProtocol(SessionProtocol.HTTP);
    ctx.setEndpointGroup(Endpoint.of("endpoint.example.com", 8080));
    return delegate.execute(ctx, req);
    };
    WebClient client = WebClient.of(preprocessor);
  • Enhanced RPC Tracing with Brave: You can now use BraveRpcService to apply fine-grained sampling, tags and annotations based on RpcRequest and RpcResponse content. #6084 #6115

    RpcTracing rpcTracing =
    RpcTracing
    .newBuilder(tracing)
    .serverSampler(req -> {
    ServiceRequestContext ctx = (ServiceRequestContext) req.unwrap();
    RpcRequest rpcRequest = ctx.rpcRequest();
    if (rpcRequest != null &&
    "SlowService".equals(rpcRequest.serviceName())) {
    // Always sample requests to the SlowService.
    return true;
    }
    return null;
    })
    .build();
    BraveRpcService.newDecorator(tracing);
  • Default Content Logging for AnnotatedService: AnnotatedService now sets request content and response content to RequestLog by default. #5711 #6231

    • You can disable this behavior by specifying -Dcom.linecorp.armeria.annotatedServiceContentLogging=false JVM option.
  • Periodic TLS Key Pair Refresh: You can now periodically refresh a TlsKeyPair using the new TlsProvider.ofScheduled() method. #6331

    File keyFile = ...;
    Fie certFile = ...;
    TlsProvider.ofScheduled(() -> {
    return TlsKeyPair.of(keyFile, certFile);
    }, Duration.ofHours(1));
  • Access gRPC Call Details in Decorators: GrpcClientCall allows you to access MethodDescriptor and CallOptions of a gRPC call within the client decorators. #6291

    GrpcClients
    .builder(grpcServerUri)
    .decorator((delegate, ctx, req) -> {
    CallOptions options = GrpcClientCall.callOptions(ctx); // 👈👈👈
    MethodDescriptor descriptor = GrpcClientCall.methodDescriptor(ctx);
    boolean retryable = descriptor.isIdempotent() || descriptor.isSafe()
    ...

    return delegate.execute(ctx, req);
    })
    .build(MyGrpcStub.class);
  • Composable Connection Pool Listeners: You can now compose multiple ConnectionPoolListeners together using ConnectionPoolListener.andThen() #5159 #6207

  • Response Headers in DocsService: The debug console in DocsService now exposes response headers. #6191

  • Multi-value Query Parameters in Annotated Service: You can now use @Param Map<String, List<?>> in AnnotatedService to collect multi-value query parameters. #6118

📈 Improvements

🛠️ Bug fixes

  • Fixed a bug where HTTP/2 flow control did not work properly, and stream-level windowing was ignored. #6253 #6266
  • Fixed a regression introduced in version 1.32.4 where RetryingClient would drop trailers from streaming responses. #6213 #6307
  • An unnecessary RST_STREAM frame is no longer sent by the server after an endStream frame has already been sent. #6279
  • CertificateMetrics now prefers the subject alternative name over the common name for the hostname tag. #6332
  • Fixes a bug that a recovered HttpResponse does not produce response content preview. #3969 #6269
  • Thrift DocService now skips a Thrift-JSON generated file that does not have namespaces. #6248
  • Fixed a bug where WatcherException: too old resource version was thrown when using KubernetesEndpointGroup. #6305
  • DnsCache no longer retains references to closed DNS resolvers. #6173 #6174
  • HttpJsonTranscodingService now correctly handles requests with an empty content body. #6319 #6325
  • Fixed GsonGrpcJsonMarshallerBulider to correctly customize JsonFormat.Parser and JsonFormat.Printer. #6146
  • CompositeEndpointGroup now handles concurrent updates correctly. #6220
  • WebSocket upgrade requests with multiple Connection header values are now handled correctly. #5957 #5958
  • Armeria now gracefully rejects invalid Forwarded header chunks with 404 Bad request. #6284 #6285

🏚️ Deprecations

☢️ Breaking changes

  • Netty 4.1.x is no longer supported. Please upgrade to Netty 4.2.x. #6335
  • armeria-kubernetes module now requires Java 11 or later. #6271
  • armeria-graphql module now requires Java 11 or later. #6335
  • io_uring transport now requires Java 9 or later. #6339
  • The common.name tag in CertificateMetrics is renamed to hostname. #6332

⛓ Dependencies

  • Blockhound 1.0.10 → 1.0.13
  • Brave 6.1.0 → 6.3.0
  • Micrometer context propagation 1.1.2 → 1.1.3
  • Control plane 1.0.48 → 1.0.49
  • Curator 5.7.1 → 5.9.0
  • Dropwizard Metrics 4.2.28 → 4.2.33
  • Eureka 2.0.4 → 2.0.5
  • Graphql-Java 20.4 → 24.2
  • gRPC-Java 1.70.0 → 1.74.0
  • Jackson 2.18.2 → 2.19.2
  • Jetty 11.0.24 → 11.0.25,, 12.0.14 → 12.0.23
  • JUnit 5.12.0 → 5.13.4
  • Kubernetes client 6.13.5 -> 7.3.1
  • krotodc 1.1.1 → 1.2.0
  • Logback 1.5.16 → 1.5.18
  • Micrometer 1.14.4 → 1.15.2
  • Micrometer Tracing 1.4.3 → 1.5.2
  • Netty 4.1.118 → 4.2.3
  • Prometheus 1.3.6 → 1.3.10
  • Reactor 3.7.3 → 3.7.8
  • Retrofit 2.11.0 → 2.12.0
  • RxJava 3.1.10 → 3.1.11
  • Sangria 4.2.5 → 4.2.10
  • Scala 3.6.1 → 3.7.1
  • Snappy 1.1.10.7 → 1.1.10.8
  • Spring 6.2.3 → 6.2.9
  • Spring Boot 3.4.3 → 3.5.4
  • Thrift 0.21.1, 0.22.0
  • Zookeeper 3.9.2 → 3.9.3

🙇 Thank you

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

@codefromthecrypt@ikhoon@haneepark@kwondh5217@kojilin@schiemon@pppurple@yzfeng2020@friscoMad@0x1306e6d@Ivan-Montes@hyunw9@AnyRoad@DongHyukki@divijvaidya@trustin@minwoox@icepeppermint@rickyma@sh-cho@blue-hope@KarboniteKream@jrhee17@anuraaga

Like Armeria?
Star us ⭐️

×