Skip to content

Spurious ERROR log "Operator called default onErrorDropped" with CancellationException when using multiple JWKS providers #2143

Description

@kniewiadomski

Expected Behavior

When a JWT token is successfully validated by one JWKS provider, the redundant request to the other provider may be cancelled internally, but no ERROR-level logs should be produced.

Cancellation of the secondary JWKS request should be treated as a normal control flow event and should not produce stack traces.

Actual Behaviour

Description

When multiple JWKS signature providers are configured, validating a JWT token produces an ERROR-level log from Reactor’s Operators.onErrorDropped containing a CancellationException.

The token validation itself succeeds. The error appears to be caused by the framework cancelling an in-flight HTTP request to a secondary JWKS endpoint after the token has already been validated using the first provider.

This results in ERROR-level stack traces being logged during normal operation, particularly during JWKS cache refresh cycles.

Actual Behavior

When one JWKS provider successfully validates the token, the request to the other provider is cancelled. This cancellation propagates through Reactor and results in the following ERROR log:

ERROR reactor.core.publisher.Operators - Operator called default onErrorDropped java.util.concurrent.CompletionException: java.util.concurrent.CancellationException: Request cancelled at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:368) ... at java.net.http/jdk.internal.net.http.Http1Exchange.cancelImpl(Http1Exchange.java:591) Caused by: java.util.concurrent.CancellationException: Request cancelled at java.net.http/jdk.internal.net.http.MultiExchange.cancel(MultiExchange.java:285)

The token validation itself succeeds, but the cancellation error is dropped and logged by Reactor’s Operators.onErrorDropped.

This occurs on every JWKS cache refresh cycle, producing repeated ERROR-level logs during normal operation.

Workaround

A workaround is to install a global Reactor hook to suppress dropped CancellationExceptions:

Hooks.onErrorDropped(error -> {
    if (error instanceof CancellationException) {
        log.debug("Dropped cancellation", error);
    } else {
        log.error("Dropped error", error);
    }
});

Steps To Reproduce

  1. Configure two JWKS providers.
micronaut:
  security:
    token:
      jwt:
        signatures:
          jwks:
            keycloak:
              url: "https://my-keycloak.example.com/realms/my-realm/protocol/openid-connect/certs"
            entra:
              url: "https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys"
  1. Use micronaut-http-client-jdk as the HTTP client.
  2. Send a request with a JWT token that can be validated using one of the JWKS providers.
  3. When the JWKS cache refresh occurs, observe the logs.

Environment Information

Java version: 21
HTTP Client: micronaut-http-client-jdk
Operating System: Linux / macOS

Example Application

No response

Version

Micronaut Platform: 4.10.8 Micronaut Security: 4.17.1

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions