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
- Call hubConnection.remove('Send') before re-registering, or ensure only one result-producing handler exists per target.
- 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.
- 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
- Call conn.remove(target) before re-registering a result handler.
- Reserve .map(...) for the single result-producing handler; use .on(...) for broadcast subscriptions.
- Make registration code idempotent in reconnect/startup paths.
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
- Invocation provides argument(s) but target expects .
- The 'invoke' method cannot be called if the connection is…
- The 'send' method cannot be called if the connection is not…
- A valid url is required.
- A valid url is required.
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)