apache/cassandra · error · IllegalStateException
Unable to start because the node was drained.
Error message
Unable to start %s because the node was drained.
What it means
After `nodetool drain` completes, the node is marked SHUTDOWN and checkServiceAllowedToStart(service) blocks any attempt to start services on the drained JVM. A drained node must be restarted as a whole process; services cannot be re-enabled in place. Thrown as IllegalStateException so nodetool renders it to the operator.
Solutions
- Fully restart the Cassandra process (systemctl restart cassandra); drain is not reversible in-place
- Avoid drain for routine restarts - use decommission for removal or plain stop/start otherwise
- Fix automation that issues enablebinary/enablethrift after drain
- Verify with `nodetool statusbinary` and operation mode in logs before attempting to start services
Example fix
// before: attempt to re-enable transport on drained node nodetool enablebinary # IllegalStateException // after: restart the process sudo systemctl restart cassandra && nodetool statusbinary
Defensive patterns
Strategy: try-catch
Validate before calling
ObjectName ss = new ObjectName("org.apache.cassandra.db:type=StorageService");
boolean shutdown = (boolean) mbs.getAttribute(ss, "IsShutdown");
if (shutdown) performFullRestart(); Try / catch
try { enableNativeTransport(); } catch (IllegalStateException e) { if (e.getMessage().contains("was drained")) { triggerProcessRestart(); } else { throw e; } } Prevention
- Treat drain as one-way until JVM restart
- Use plain stop/start for maintenance that must resume
- Add health checks that detect SHUTDOWN operation mode
When it happens
Trigger: Calling startRPCServer()/start native transport (or nodetool enablebinary/enablethrift) on a node whose drain completed (isShutdown() == true).
Common situations: Trying to 'un-drain' a node via nodetool; monitoring/auto-healing scripts re-enabling transports after maintenance that ended with drain.
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
- Cannot drain node (did it already happen?)
- No local state, state is in silent shutdown, or node hasn't…
- SSTables import has been aborted
- Unable to start because the node is draining.
- Can not fetch log entries during shutdown
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/123538bf19f276da.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/service/StorageService.java:4160
*/
public synchronized boolean removePostShutdownHook(Runnable hook)
{
return postShutdownHooks.remove(hook);
}
/**
* Some services are shutdown during draining and we should not attempt to start them again.
*
* @param service - the name of the service we are trying to start.
* @throws IllegalStateException - an exception that nodetool is able to convert into a message to display to the user
*/
synchronized void checkServiceAllowedToStart(String service)
{
if (isDraining()) // when draining isShutdown is also true, so we check first to return a more accurate message
throw new IllegalStateException(String.format("Unable to start %s because the node is draining.", service));
if (isShutdown()) // do not rely on operationMode in case it gets changed to decommissioned or other
throw new IllegalStateException(String.format("Unable to start %s because the node was drained.", service));
if (!isNormal() && joinRing) // if the node is not joining the ring, it is gossipping-only member which is in STARTING state forever
throw new IllegalStateException(String.format("Unable to start %s because the node is not in the normal state.", service));
}
// Never ever do this at home. Used by tests.
@VisibleForTesting
public IPartitioner setPartitionerUnsafe(IPartitioner newPartitioner)
{
checkNotNull(newPartitioner, "newPartitioner is null");
checkState(originalPartitioner == null, "Already changed the partitioner without resetting");
originalPartitioner = DatabaseDescriptor.setPartitionerUnsafe(newPartitioner);
valueFactory = new VersionedValue.VersionedValueFactory(newPartitioner);
return originalPartitioner;
}
@VisibleForTesting
public void resetPartitionerUnsafe()View on GitHub (pinned to 88fd0f6a0e)