dotnet/aspnetcore · error · RuntimeException

' ' already has a value returning handler. Multiple return…

Error message

'%s' already has a value returning handler. Multiple return values are not supported.

What it means

Thrown by CallbackMap.put when registering a second handler that returns a value (getHasResult()==true) for the same server-to-client method target. The Java client lets multiple handlers subscribe to one target (broadcast style), but only one of them may produce a return value, because the server expects a single completion per invocation id.

Solutions

  1. Call hubConnection.remove('Send') before re-registering, or ensure only one result-producing handler exists per target.
  2. Use hubConnection.on(...) for fire-and-forget subscriptions that do not return a value; reserve .map(...) for the single handler that must return a result.
  3. Audit registrations in startup/reconnect code to confirm they are idempotent (remove before add).

Example fix

// before
hubConnection.map("GetTime", () -> Instant.now());
hubConnection.map("GetTime", () -> Instant.now()); // throws

// after
hubConnection.remove("GetTime");
hubConnection.map("GetTime", () -> Instant.now());
Defensive patterns

Strategy: validation

Validate before calling

void ensureSingleResultHandler(HubConnection conn, String target, Action1<Object> handler) {
  conn.remove(target); // idempotent: clear any prior result handler
}

// before re-registering:
conn.remove("GetTime");
conn.map("GetTime", () -> Instant.now());

Type guard

// Java has no first-class type guards; emulate with a registry check.
boolean hasResultHandler(CallbackMap map, String target) {
  return map.get(target).stream().anyMatch(InvocationHandler::getHasResult);
}

Try / catch

try {
  conn.map(target, handler);
} catch (RuntimeException e) {
  if (e.getMessage() != null && e.getMessage().contains("already has a value returning handler")) {
    conn.remove(target);
    conn.map(target, handler); // now safe
  } else throw e;
}

Prevention

When it happens

Trigger: Calling hubConnection.on('Send', msg -> ...) twice where the lambda returns a value (or registering via a Type that makes the handler a 'result' handler); calling .map('Send', ...) twice; mixing .on and .map for the same target where both produce results.

Common situations: Refactor duplicated an .on registration without removing the old one; a base class and subclass both subscribe to the same method with a return type; using .map (single-result) for a target already handled by another .map.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/4194c134f659da2f. Report an issue: GitHub.

Appendix: source

Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/CallbackMap.java:30

class CallbackMap {
    private final Map<String, List<InvocationHandler>> handlers = new HashMap<>();
    private final ReentrantLock lock = new ReentrantLock();

    public InvocationHandler put(String target, Object action, Type... types) {
        try {
            lock.lock();
            InvocationHandler handler = new InvocationHandler(action, types);
            if (!handlers.containsKey(target)) {
                handlers.put(target, new ArrayList<>());
            }

            List<InvocationHandler> methodHandlers;
            methodHandlers = handlers.get(target);
            if (handler.getHasResult()) {
                for (InvocationHandler existingHandler : methodHandlers) {
                    if (existingHandler.getHasResult()) {
                        throw new RuntimeException(String.format("'%s' already has a value returning handler. Multiple return values are not supported.", target));
                    }
                }
            }
            methodHandlers = new ArrayList<>(methodHandlers);
            methodHandlers.add(handler);

            // replace List in handlers map
            handlers.remove(target);
            handlers.put(target, methodHandlers);
            return handler;
        } finally {
            lock.unlock();
        }
    }

    public List<InvocationHandler> get(String key) {
        try {
            lock.lock();

View on GitHub (pinned to 3600ca084e)