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

  1. Inspect the raw negotiate JSON response (enable TRACE logging or hit the /negotiate URL with curl) and confirm which strings appear in availableTransports.
  2. On the server, ensure WebSockets (and/or LongPolling) is enabled in Configure<HubEndpointOptions> / AddSignalR(o => o.EnableDetailedErrors = true) and that IIS/Kestrel allows WebSockets.
  3. Upgrade the signalr Java client to a version matching the server's protocol version so the advertised transport names line up.
  4. 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

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


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)