{"record":{"id":"4f8278baad66f235","repo":"dotnet/aspnetcore","slug":"negotiation-can-only-be-skipped-when-using-the-web-4f8278","errorCode":null,"errorMessage":"Negotiation can only be skipped when using the WebSocket transport directly with '.withTransport(TransportEnum.WEBSOCKETS)' on the 'HubConnectionBuilder'.","messagePattern":"Negotiation can only be skipped when using the WebSocket transport directly with '\\.withTransport\\(TransportEnum\\.WEBSOCKETS\\)' on the 'HubConnectionBuilder'\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java","lineNumber":276,"sourceCode":"                tokenCompletable.onError(error);\n            });\n\n            Single<NegotiateResponse> negotiate = null;\n            if (!skipNegotiate) {\n                negotiate = tokenCompletable.andThen(Single.defer(() -> startNegotiate(baseUrl, 0, localHeaders)));\n            } else {\n                negotiate = tokenCompletable.andThen(Single.defer(() -> Single.just(new NegotiateResponse(baseUrl))));\n            }\n\n            negotiate.flatMapCompletable(negotiateResponse -> {\n                logger.debug(\"Starting HubConnection.\");\n                Transport transport = customTransport;\n                if (transport == null) {\n                    Single<String> tokenProvider = negotiateResponse.getAccessToken() != null ? Single.just(negotiateResponse.getAccessToken()) : accessTokenProvider;\n                    TransportEnum chosenTransport;\n                    if (this.skipNegotiate) {\n                        if (this.transportEnum != TransportEnum.WEBSOCKETS) {\n                            throw new RuntimeException(\"Negotiation can only be skipped when using the WebSocket transport directly with '.withTransport(TransportEnum.WEBSOCKETS)' on the 'HubConnectionBuilder'.\");\n                        }\n                        chosenTransport = this.transportEnum;\n                    } else {\n                        chosenTransport = negotiateResponse.getChosenTransport();\n                    }\n                    switch (chosenTransport) {\n                        case LONG_POLLING:\n                            transport = new LongPollingTransport(localHeaders, httpClient, tokenProvider);\n                            break;\n                        default:\n                            transport = new WebSocketTransport(localHeaders, httpClient);\n                    }\n                }\n\n                connectionState.transport = transport;\n\n                transport.setOnReceive(this.callback);\n                transport.setOnClose((message) -> stopConnection(message));","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java#L258-L294","documentation":"Thrown during start() when skipNegotiate is true but the configured transportEnum is not WEBSOCKETS. Negotiation can be skipped only when connecting straight to WebSockets, because the negotiate response is what tells the client which of the other transports (LongPolling) the server supports. Any other transport requires the negotiate handshake first.","triggerScenarios":"HubConnectionBuilder is configured with .withTransport(TransportEnum.LONG_POLLING) (or ALL) AND skipNegotiate=true. The branch in start() checks this combination and throws before opening a transport.","commonSituations":"Copy/paste from a WebSockets-only sample that set skipNegotiate; misconfigured builder that combined .shouldSkipNegotiate() with a non-WebSocket transport; trying to optimize connection time by skipping negotiate but keeping LongPolling as a fallback.","solutions":["If you must skip negotiate, also pin the transport: .withTransport(TransportEnum.WEBSOCKETS).withSkipNegotiate(true).","Otherwise, allow negotiate: do not call withSkipNegotiate(true) when using LongPolling or ALL.","Re-read the builder chain and confirm skipNegotiate and transportEnum are consistent."],"exampleFix":"// before\nHubConnection conn = HubConnectionBuilder.create(url)\n  .withTransport(TransportEnum.LONG_POLLING)\n  .withSkipNegotiate(true) // throws on start()\n  .build();\n\n// after (option A: keep skip, pin websockets)\nHubConnection conn = HubConnectionBuilder.create(url)\n  .withTransport(TransportEnum.WEBSOCKETS)\n  .withSkipNegotiate(true)\n  .build();\n\n// after (option B: keep long polling, run negotiate)\nHubConnection conn = HubConnectionBuilder.create(url)\n  .withTransport(TransportEnum.LONG_POLLING)\n  .build(); // skipNegotiate defaults to false","handlingStrategy":"validation","validationCode":"HubConnection build(boolean skipNegotiate, TransportEnum transport, String url) {\n  if (skipNegotiate && transport != TransportEnum.WEBSOCKETS) {\n    throw new IllegalArgumentException(\"skipNegotiate requires WebSockets\");\n  }\n  HubConnectionBuilder b = HubConnectionBuilder.create(url);\n  b.withTransport(transport);\n  if (skipNegotiate) b.withSkipNegotiate(true);\n  return b.build();\n}","typeGuard":"boolean skipNegotiateCompatible(TransportEnum t) {\n  return t == TransportEnum.WEBSOCKETS;\n}","tryCatchPattern":"try {\n  conn.start().blockingAwait();\n} catch (RuntimeException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Negotiation can only be skipped\")) {\n    // either set transport to WEBSOCKETS, or remove withSkipNegotiate(true)\n  } else throw e;\n}","preventionTips":["Only combine skipNegotiate with TransportEnum.WEBSOCKETS.","Review the builder chain to ensure transport and skipNegotiate are consistent.","If using LongPolling/ALL, leave negotiate enabled (default)."],"tags":["signalr","java","configuration","negotiate","transport"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}