dotnet/aspnetcore · error · RuntimeException
There were no compatible transports on the server.
Error message
There were no compatible transports on the server.
What it means
Thrown during the SignalR negotiate handshake when the client is configured with TransportEnum.ALL but the server's negotiate response lists neither 'WebSockets' nor 'LongPolling' in its available transports. The client iterates the server-advertised transport set and falls through every supported option, so there is no transport it can use to establish the connection.
Solutions
- Inspect the raw negotiate JSON response (enable TRACE logging or hit the /negotiate URL with curl) and confirm which strings appear in availableTransports.
- On the server, ensure WebSockets (and/or LongPolling) is enabled in Configure<HubEndpointOptions> / AddSignalR(o => o.EnableDetailedErrors = true) and that IIS/Kestrel allows WebSockets.
- Upgrade the signalr Java client to a version matching the server's protocol version so the advertised transport names line up.
- If the server genuinely only offers an unsupported transport, switch to the JavaScript or .NET client which support SSE, or run a WebSocket-capable host.
Example fix
// before
HubConnectionBuilder.create("https://hub.example")
.build(); // TransportEnum.ALL by default
// after - confirm server offers WebSockets, then pin it
HubConnectionBuilder.create("https://hub.example")
.withTransport(TransportEnum.WEBSOCKETS)
.build(); Defensive patterns
Strategy: validation
Validate before calling
// Before start(), probe the negotiate endpoint and confirm a usable transport.
// (pseudo - use your HTTP client)
Response r = http.post(url + "/negotiate", headers);
NegotiateJson body = parse(r);
if (!body.availableTransports.contains("WebSockets")
&& !body.availableTransports.contains("LongPolling")) {
throw new IllegalStateException(
"Server offers no Java-compatible transport: " + body.availableTransports);
} Try / catch
// Wrap start() and surface the transport failure clearly.
hub.start()
.subscribe(
() -> { /* connected */ },
err -> {
if (err.getMessage().contains("no compatible transports")) {
// log availableTransports, alert ops to enable WebSockets/LongPolling
}
}); Prevention
- Run a /negotiate smoke test in CI against the staging hub to assert availableTransports.
- Pin the server transport configuration in code review so nobody disables WebSockets silently.
- Document the required transport set next to the HubConnectionBuilder call.
When it happens
Trigger: Calling HubConnection.start() (which triggers negotiate) when the configured transport is TransportEnum.ALL and the server's /negotiate response 'availableTransports' array contains no entry matching 'WebSockets' or 'LongPolling' (e.g. server only advertises ServerSentEvents, which the Java client does not implement).
Common situations: Server-side Azure SignalR Service or ASP.NET Core hub configured to only allow SSE or a custom transport; version mismatch where a newer server advertises transports the older Java client cannot map; reverse proxy stripping the negotiate response; the hub endpoint requires Azure SignalR but the negotiate endpoint returned a minimal payload.
Related errors
- Error reading NegotiateResponse
- Negotiate redirection limit exceeded.
- ${negotiateResponse.getError()}
- Negotiation can only be skipped when using the WebSocket…
- Unexpected status code returned from negotiate
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/66c6120675ffcf1f.
Report an issue: GitHub.
Appendix: source
Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java:385
private Single<NegotiateResponse> startNegotiate(String url, int negotiateAttempts, Map<String, String> localHeaders) {
if (this.state.getHubConnectionState() != HubConnectionState.CONNECTING) {
throw new RuntimeException("HubConnection trying to negotiate when not in the CONNECTING state.");
}
return handleNegotiate(url, localHeaders).flatMap(response -> {
if (response.getRedirectUrl() != null && negotiateAttempts >= MAX_NEGOTIATE_ATTEMPTS) {
throw new RuntimeException("Negotiate redirection limit exceeded.");
}
if (response.getRedirectUrl() == null) {
Set<String> transports = response.getAvailableTransports();
if (this.transportEnum == TransportEnum.ALL) {
if (transports.contains("WebSockets")) {
response.setChosenTransport(TransportEnum.WEBSOCKETS);
} else if (transports.contains("LongPolling")) {
response.setChosenTransport(TransportEnum.LONG_POLLING);
} else {
throw new RuntimeException("There were no compatible transports on the server.");
}
} else if (this.transportEnum == TransportEnum.WEBSOCKETS && !transports.contains("WebSockets") ||
(this.transportEnum == TransportEnum.LONG_POLLING && !transports.contains("LongPolling"))) {
throw new RuntimeException("There were no compatible transports on the server.");
} else {
response.setChosenTransport(this.transportEnum);
}
String connectionToken = "";
if (response.getVersion() > 0) {
this.state.getConnectionState().connectionId = response.getConnectionId();
connectionToken = response.getConnectionToken();
} else {
connectionToken = response.getConnectionId();
this.state.getConnectionState().connectionId = connectionToken;
}
String finalUrl = Utils.appendQueryString(url, "id=" + connectionToken);View on GitHub (pinned to 3600ca084e)