dotnet/aspnetcore · error · RuntimeException

The HubConnection failed to transition from the '%s' state t

Error message

The HubConnection failed to transition from the '%s' state to the '%s' state because it was actually in the '%s' state.

What it means

Thrown by the internal changeState(from, to) guard when the connection's actual HubConnectionState does not equal the expected 'from' state. It signals a state-machine inconsistency, almost always caused by concurrent or re-entrant start()/stop()/automatic-reconnect operations racing on the same HubConnection. The start() error handler at HubConnection.java:350-353 intentionally swallows it, so it normally surfaces only outside that guarded path.

Source

Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java:1717

                }
                return this.state;
            } finally {
                this.lock.unlock();
            }
        }

        public HubConnectionState getHubConnectionState() {
            return this.hubConnectionState;
        }

        public void changeState(HubConnectionState from, HubConnectionState to) {
            this.lock.lock();
            try {
                logger.debug("The HubConnection is attempting to transition from the {} state to the {} state.", from, to);
                if (this.hubConnectionState != from) {
                    logger.debug("The HubConnection failed to transition from the {} state to the {} state because it was actually in the {} state.",
                        from, to, this.hubConnectionState);
                    throw new RuntimeException(String.format("The HubConnection failed to transition from the '%s' state to the '%s' state because it was actually in the '%s' state.",
                        from, to, this.hubConnectionState));
                }

                this.hubConnectionState = to;
            } finally {
                this.lock.unlock();
            }
        }

        public void changeState(HubConnectionState to) {
            this.lock.lock();
            try {
                logger.debug("The HubConnection is transitioning from the {} state to the {} state.", this.hubConnectionState, to);
                this.hubConnectionState = to;
            } finally {
                this.lock.unlock();
            }
        }

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Serialize lifecycle calls: block on the previous start()/stop() Completable before issuing the next.
  2. Guard with hubConnection.getHubConnectionState() before calling start()/stop().
  3. Coordinate/disable withAutomaticReconnect when you manually manage connection lifecycle.
  4. Avoid issuing start() on an instance that is already CONNECTING/CONNECTED.

Example fix

// before
hubConnection.start().subscribe(() -> {}, e -> {});
hubConnection.start().subscribe(() -> {}, e -> {}); // races

// after
hubConnection.start().blockingAwait();
// only start again after stop().blockingAwait()
Defensive patterns

Strategy: validation

Validate before calling

// Java - check state before lifecycle calls
if (hubConnection.getHubConnectionState() == HubConnectionState.DISCONNECTED) {
    hubConnection.start().blockingAwait();
}

Try / catch

// Wrap a contested lifecycle op; this error is usually swallowed internally,
// so prefer preventing the race over catching it.
try {
    hubConnection.start().blockingAwait();
} catch (RuntimeException ex) {
    // log and reconcile state before retrying
}

Prevention

When it happens

Trigger: Calling start() while a previous start() is still mid-handshake; calling stop() during the CONNECTING->CONNECTED transition at line 324; withAutomaticReconnect racing a manual stop(); invoking start() twice in quick succession without waiting on the first Completable.

Common situations: Background reconnect logic overlapping a manual reconnect; a UI 'Connect' button that allows double-tap; withAutomaticReconnect enabled combined with manual stop/start cycling; reusing a HubConnection instance across rapid lifecycle changes.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/5a7267bac6679f28. Report an issue: GitHub.