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

  1. Fully restart the Cassandra process (systemctl restart cassandra); drain is not reversible in-place
  2. Avoid drain for routine restarts - use decommission for removal or plain stop/start otherwise
  3. Fix automation that issues enablebinary/enablethrift after drain
  4. 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

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


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)