{"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":"validation","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/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java#L258-L294","documentation":"A RuntimeException thrown during start() when skipNegotiate is enabled (via .withSkipNegotiate() on the builder) but the chosen transport is not WebSocket. Negotiation can be skipped only for the WebSocket transport because other transports (LongPolling) require the connection token obtained from negotiate. The guard prevents an invalid configuration from proceeding.","triggerScenarios":"Building a HubConnection with .shouldSkipNegotiate(true) (or equivalent) while also selecting TransportEnum.LONG_POLLING or TransportEnum.ALL via .withTransport(...), then calling start().","commonSituations":"A developer enables skipNegotiate for performance or to bypass a negotiate endpoint that is blocked, but forgets to also pin the transport to WEBSOCKETS. Copying a builder snippet from one context (WebSocket-only service) into another that expects LongPolling. Misunderstanding that skipNegotiate implies direct WebSocket connection.","solutions":["Add .withTransport(TransportEnum.WEBSOCKETS) to the builder when using .withSkipNegotiate(true) / skipNegotiate.","If you need LongPolling, remove skipNegotiate so the normal negotiate flow runs.","Re-read the builder configuration to confirm transport and skipNegotiate are consistent."],"exampleFix":"// before: skipNegotiate without pinning WebSocket\nHubConnection conn = HubConnectionBuilder.create(url)\n    .withSkipNegotiate(true)\n    .withTransport(TransportEnum.LONG_POLLING)\n    .build();\n\n// after: skipNegotiate requires WebSockets\nHubConnection conn = HubConnectionBuilder.create(url)\n    .withSkipNegotiate(true)\n    .withTransport(TransportEnum.WEBSOCKETS)\n    .build();","handlingStrategy":"validation","validationCode":"// Validate builder configuration before build/start.\nboolean skipNegotiate = ...; TransportEnum te = ...;\nif (skipNegotiate && te != TransportEnum.WEBSOCKETS) {\n    throw new IllegalStateException(\"skipNegotiate requires TransportEnum.WEBSOCKETS\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    connection.start().blockingAwait();\n} catch (RuntimeException e) {\n    if (e.getMessage().contains(\"Negotiation can only be skipped\")) {\n        // rebuild with WebSocket transport or remove skipNegotiate\n    } else { throw e; }\n}","preventionTips":["When using skipNegotiate, always pair it with .withTransport(TransportEnum.WEBSOCKETS).","Centralize builder construction in one factory method that enforces consistent options.","Add a unit test asserting builder options are consistent before start()."],"tags":["signalr","java","configuration","transport","negotiate","websocket"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}