{"record":{"id":"0b7c297f86fb0baa","repo":"dotnet/aspnetcore","slug":"hubconnection-trying-to-negotiate-when-not-in-the","errorCode":null,"errorMessage":"HubConnection trying to negotiate when not in the CONNECTING state.","messagePattern":"HubConnection trying to negotiate when not in the CONNECTING state\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java","lineNumber":369,"sourceCode":"                    }\n                // this error is already logged and we want the user to see the original error\n                } catch (Exception ex) {\n                } finally {\n                    this.state.unlock();\n                }\n\n                localStart.onError(error);\n            });\n        } finally {\n            this.state.lock.unlock();\n        }\n\n        return localStart;\n    }\n\n    private Single<NegotiateResponse> startNegotiate(String url, int negotiateAttempts, Map<String, String> localHeaders) {\n        if (this.state.getHubConnectionState() != HubConnectionState.CONNECTING) {\n            throw new RuntimeException(\"HubConnection trying to negotiate when not in the CONNECTING state.\");\n        }\n\n        return handleNegotiate(url, localHeaders).flatMap(response -> {\n            if (response.getRedirectUrl() != null && negotiateAttempts >= MAX_NEGOTIATE_ATTEMPTS) {\n                throw new RuntimeException(\"Negotiate redirection limit exceeded.\");\n            }\n\n            if (response.getRedirectUrl() == null) {\n                Set<String> transports = response.getAvailableTransports();\n                if (this.transportEnum == TransportEnum.ALL) {\n                    if (transports.contains(\"WebSockets\")) {\n                        response.setChosenTransport(TransportEnum.WEBSOCKETS);\n                    } else if (transports.contains(\"LongPolling\")) {\n                        response.setChosenTransport(TransportEnum.LONG_POLLING);\n                    } else {\n                        throw new RuntimeException(\"There were no compatible transports on the server.\");\n                    }\n                } else if (this.transportEnum == TransportEnum.WEBSOCKETS && !transports.contains(\"WebSockets\") ||","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java#L351-L387","documentation":"Thrown by startNegotiate when the HubConnection's state is not CONNECTING at the moment negotiate is attempted. This is a state-machine invariant: start() transitions to CONNECTING and only then should call startNegotiate. Seeing this throw means the flow reached negotiate from an unexpected state - a coding error inside the client flow or a race that mutated state out from under the negotiate step.","triggerScenarios":"Internal: start()'s flow called startNegotiate after the connection already left CONNECTING (e.g. a concurrent stop()/reconnect raced and moved state to DISCONNECTED or CONNECTED). The user-facing trigger is usually calling stop() during start(), or a reconnect kicking off negotiate on an already-connected hub.","commonSituations":"Concurrent start()/stop() on the same HubConnection; a reconnect policy that fires while the initial start is still negotiating; calling start() twice without stop() in between on a buggy state path.","solutions":["Serialize connection lifecycle calls: do not call stop() (or a second start()) while start() is in flight; chain them with the Single/Completable the API returns.","If using an automatic reconnect policy, review its trigger so it does not re-enter negotiate while the original start() is unresolved.","Upgrade the client - races around the CONNECTING state are typically fixed in patch releases; pin to the latest matching version."],"exampleFix":"// before\nconn.start().subscribe();\nconn.stop().blockingAwait(); // races with start's negotiate -> can throw\n\n// after\nconn.start().blockingAwait();\nconn.stop().blockingAwait();","handlingStrategy":"validation","validationCode":"void safeStart(HubConnection conn) {\n  if (conn.getConnectionState() != HubConnectionState.DISCONNECTED) {\n    throw new IllegalStateException(\"already connecting/connected\");\n  }\n  conn.start().blockingAwait();\n}","typeGuard":"boolean isDisconnected(HubConnection conn) {\n  return conn.getConnectionState() == HubConnectionState.DISCONNECTED;\n}","tryCatchPattern":"try {\n  conn.start().blockingAwait();\n} catch (RuntimeException e) {\n  if (\"HubConnection trying to negotiate when not in the CONNECTING state.\".equals(e.getMessage())) {\n    // serialize lifecycle calls; do not call stop()/start() concurrently\n  } else throw e;\n}","preventionTips":["Serialize start/stop/reconnect; never invoke them concurrently on one HubConnection.","Chain lifecycle on the returned Single/Completable rather than fire-and-forget.","Keep the client version current - CONNECTING-state races are fixed in patch releases."],"tags":["signalr","java","state-machine","race","connection-lifecycle"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}