quarkusio/quarkus · error · WebSocketServerException

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

Error message

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

What it means

Thrown by WebSocketProcessor.validateOnPingMessage when a Kotlin @OnPingMessage callback has an invalid return type: the callback is neither void, Uni<Void>, nor a Kotlin suspend function returning Unit. Ping callbacks only acknowledge the frame; any other return value has nowhere to go, so build-time validation aborts with the callback description appended to the message.

Source

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

            if (enclosingClass.nestingType() == NestingType.INNER) {
                return mergePath(getPathPrefix(index, enclosingClass.enclosingClass()), path);
            } else {
                return path.endsWith("/") ? path.substring(path.length() - 1) : path;
            }
        }
        return "";
    }

    private void validateOnPingMessage(Callback callback) {
        if (KotlinUtils.isKotlinMethod(callback.method)) {
            if (!callback.isReturnTypeVoid() && !isUniVoid(callback.returnType())
                    && !callback.isKotlinSuspendFunctionReturningUnit()) {
                throw new WebSocketServerException(
                        "@OnPingMessage callback must return Unit or Uni<Void>: " + callback.asString());
            }
        } else {
            if (!callback.isReturnTypeVoid() && !isUniVoid(callback.returnType())) {
                throw new WebSocketServerException(
                        "@OnPingMessage callback must return void or Uni<Void>: " + callback.asString());
            }
        }
        Type messageType = callback.argumentType(MessageCallbackArgument::isMessage);
        if (messageType == null || !messageType.name().equals(WebSocketDotNames.BUFFER)) {
            throw new WebSocketServerException(
                    "@OnPingMessage callback must accept exactly one message parameter of type io.vertx.core.buffer.Buffer: "
                            + callback.asString());
        }
    }

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

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the return type to void.
  2. Or return io.smallrye.mutiny.Uni<Void> for reactive handling.
  3. Move any result-producing logic into an explicit message send within the handler body.
  4. If you need CompletionStage semantics, convert with Uni.createFrom().completionStage(...) and return Uni<Void>.

Example fix

// before
@OnPingMessage
String onPing(Buffer buffer) { return "pong"; }

// after
@OnPingMessage
void onPing(Buffer buffer) { /* handle */ }
Defensive patterns

Strategy: validation

Validate before calling

// Java pre-check: @OnPingMessage must return void or Uni<Void>
static void checkPingCallback(Method m) {
    Class<?> r = m.getReturnType();
    if (!(r == void.class || r == io.smallrye.mutiny.Uni.class))
        throw new IllegalStateException("@OnPingMessage must return void or Uni<Void>: " + m);
}

Prevention

When it happens

Trigger: A Java method annotated @OnPingMessage returns something other than void or Uni<Void> — e.g. String, Buffer, Uni<String>, CompletionStage<Void> written without Mutiny.

Common situations: Returning an echo/acknowledgement value from the ping handler; using java.util.concurrent.CompletionStage instead of Mutiny Uni; accidental return of intermediate computation results.

Related errors


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