quarkusio/quarkus · error · WebSocketServerException

@OnClose callback must return void or Uni<Void>:

Error message

@OnClose callback must return void or Uni<Void>: 

What it means

For non-Kotlin @OnClose callbacks, the method must return void or Uni<Void>. Close callbacks are invoked on connection close and their return value is discarded, so any other return type fails the build-time signature validation with WebSocketServerException.

Source

Thrown at extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java:1130

        }
        Type messageType = callback.argumentType(MessageCallbackArgument::isMessage);
        if (messageType == null || !messageType.name().equals(WebSocketDotNames.BUFFER)) {
            throw new WebSocketServerException(
                    "@OnPongMessage callback must accept exactly one message parameter of type io.vertx.core.buffer.Buffer: "
                            + callback.asString());
        }
    }

    private void validateOnClose(Callback callback) {
        if (KotlinUtils.isKotlinMethod(callback.method)) {
            if (!callback.isReturnTypeVoid() && !isUniVoid(callback.returnType())
                    && !callback.isKotlinSuspendFunctionReturningUnit()) {
                throw new WebSocketServerException(
                        "@OnClose callback must return Unit or Uni<Void>: " + callback.asString());
            }
        } else {
            if (!callback.isReturnTypeVoid() && !isUniVoid(callback.returnType())) {
                throw new WebSocketServerException(
                        "@OnClose callback must return void or Uni<Void>: " + callback.asString());
            }
        }
    }

    /**
     * The generated endpoint class looks like:
     *
     * <pre>
     * public class Echo_WebSocketEndpoint extends WebSocketEndpointBase {
     *
     *     public WebSocket.ExecutionMode executionMode() {
     *         return WebSocket.ExecutionMode.SERIAL;
     *     }
     *
     *     public Echo_WebSocketEndpoint(WebSocketConnection connection, Codecs codecs,
     *             WebSocketRuntimeConfig config, ContextSupport contextSupport, SecuritySupport securitySupport) {
     *         super(connection, codecs, config, contextSupport, securitySupport);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the return type to void
  2. For async cleanup, return Uni<Void> and complete with null
  3. If you need CompletionStage semantics, wrap it: Uni.createFrom().completionStage(...)

Example fix

// before
@OnClose
CompletionStage<Void> onClose() { return done; }

// after
@OnClose
Uni<Void> onClose() { return Uni.createFrom().completionStage(() -> done); }
Defensive patterns

Strategy: validation

Validate before calling

for (Method m : MyWebSocket.class.getDeclaredMethods()) {
    if (m.isAnnotationPresent(OnClose.class)) {
        boolean ok = m.getReturnType().equals(void.class) || Uni.class.isAssignableFrom(m.getReturnType());
        if (!ok) throw new IllegalStateException(m + " must return void or Uni<Void>");
    }
}

Prevention

When it happens

Trigger: A Java @OnClose method returning e.g. String, int, CompletionStage<Void>, io.smallrye.mutiny.Uni<String>, or any POJO.

Common situations: Returning CompletionStage instead of Mutiny Uni (not accepted); returning a status/result object; copy-pasting from @OnTextMessage handlers that return message replies.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/105b095c3a3771c2. Report an issue: GitHub.