apache/cassandra · error · RuntimeException
Stream is finished with state
Error message
Stream %s is finished with state %s
What it means
StreamSession guards every mutation (attach, transfer, message handling) with failIfFinished(), which throws if the session has already reached a final state (COMPLETED/FAILED). This prevents operating on a session whose streaming plan is over, e.g. messages or ranges arriving after completion.
Solutions
- Check session state via session.state().isFinalState() before attaching ranges/streams or sending messages.
- Create a new StreamSession (new planId) for retries instead of reusing a finished session.
- Inspect earlier logs for the session's planId to see why it reached COMPLETED/FAILED before this call.
- Fix network delays/duplicates or messaging retry settings that deliver messages after session completion.
Example fix
// before
session.messageReceived(msg); // throws if finished
// after
if (!session.state().isFinalState()) {
session.messageReceived(msg);
} else {
logger.warn("Dropping message for finished stream session {}", session.planId());
} Defensive patterns
Strategy: validation
Validate before calling
if (session.state().isFinalState()) { /* drop or start new session */ } Try / catch
try { session.messageReceived(msg); } catch (RuntimeException e) { if (e.getMessage().startsWith("Stream ") && e.getMessage().contains("is finished")) { logger.warn("Message after stream end, ignoring"); } else { throw e; } } Prevention
- Never reuse a StreamSession after it reaches a final state; create a fresh session for retries.
- Log planId in all streaming operations to trace session lifecycle.
- Suppress duplicate message delivery at the transport layer where possible.
When it happens
Trigger: Calling attachInbound/attachOutbound, addTransferRanges, addTransferStreams, messageReceived, or prepareReceiving on a StreamSession whose state is in a final state — typically a control or data message arriving after the session already completed or failed.
Common situations: Slow/late messages delivered after session success; a session that failed earlier (node down, timeout) receiving follow-up traffic; coordinator retrying a stream request against a session that already ran.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A node required to move the data consistently is down
- Can not start range streaming as all candidates
- Can't finish migration, initiator=
- Can't join the ring because bootstrap hasn't completed.
- Can't proceed from the state
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/97492b740a270d1c.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/streaming/StreamSession.java:481
//Do we need to unwrap here also or is that just making it worse?
//Range and if it's transient
RangesAtEndpoint unwrappedRanges = replicas.unwrap();
List<OutgoingStream> streams = getOutgoingStreamsForRanges(unwrappedRanges, stores, pendingRepair, previewKind);
addTransferStreams(streams);
Set<Range<Token>> toBeUpdated = transferredRangesPerKeyspace.get(keyspace);
if (toBeUpdated == null)
{
toBeUpdated = new HashSet<>();
}
toBeUpdated.addAll(replicas.ranges());
transferredRangesPerKeyspace.put(keyspace, toBeUpdated);
}
private void failIfFinished()
{
if (state().isFinalState())
throw new RuntimeException(String.format("Stream %s is finished with state %s", planId(), state().name()));
}
private Collection<ColumnFamilyStore> getColumnFamilyStores(String keyspace, Collection<String> columnFamilies)
{
Collection<ColumnFamilyStore> stores = new HashSet<>();
// if columnfamilies are not specified, we add all cf under the keyspace
if (columnFamilies.isEmpty())
{
stores.addAll(Keyspace.open(keyspace).getColumnFamilyStores());
}
else
{
for (String cf : columnFamilies)
stores.add(Keyspace.open(keyspace).getColumnFamilyStore(cf));
}
return stores;
}
View on GitHub (pinned to 88fd0f6a0e)