apache/cassandra · error · RuntimeException

No nodes present in the cluster. Has this node finished…

Error message

No nodes present in the cluster. Has this node finished starting up?

What it means

Murmur3Partitioner.describeOwnership(sortedTokens) computes ownership fractions by walking the sorted token ring; an empty ring means no tokens/nodes are registered, so ownership cannot be computed and it throws RuntimeException asking whether the node finished starting up. It reflects a cluster view with zero endpoints rather than a caller mistake in arguments.

Solutions

  1. Wait for the node to finish startup and join the ring (nodetool status shows the node UP) before querying ownership.
  2. Pass a non-empty sortedTokens collection when calling describeOwnership programmatically.
  3. In tests, seed TokenMetadata with at least one token before calling describeOwnership.
  4. Investigate join/bootstrap failures (logs) if the cluster should have nodes but the ring is empty.

Example fix

// before
Map<Token, Float> ownership = partitioner.describeOwnership(tokenMetadata.sortedTokens());
// after
Collection<Token> sorted = tokenMetadata.sortedTokens();
if (sorted.isEmpty()) {
    logger.warn("Ring is empty; node not yet joined. Skipping ownership query.");
    return;
}
Map<Token, Float> ownership = partitioner.describeOwnership(sorted);
Defensive patterns

Strategy: try-catch

Validate before calling

Collection<Token> sorted = tokenMetadata.sortedTokens();
if (sorted == null || sorted.isEmpty()) {
    logger.warn("No tokens in ring; node may still be starting.");
    return Collections.emptyMap();
}

Type guard

boolean ringIsPopulated(TokenMetadata tm) { return !tm.sortedTokens().isEmpty(); }

Try / catch

try {
    return partitioner.describeOwnership(tokenMetadata.sortedTokens());
} catch (RuntimeException e) {
    if (e.getMessage().contains("No nodes present")) return Collections.emptyMap(); // node still joining
    throw e;
}

Prevention

When it happens

Trigger: Calling describeOwnership (directly or via StorageService 'describeOwnership'/nodetool toppartitions-adjacent ownership queries) before gossip has populated token metadata, on a freshly started single node, or after a join failure leaving the ring empty.

Common situations: Monitoring scripts querying ownership immediately after node start; nodetool run before the node joined the ring; tests constructing a partitioner without registering any tokens; a cluster whose schema/token metadata hasn't loaded.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/dc52996602c80382. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/dht/Murmur3Partitioner.java:445

    private long normalize(long v)
    {
        // We exclude the MINIMUM value; see getToken()
        return v == Long.MIN_VALUE ? Long.MAX_VALUE : v;
    }

    public boolean preservesOrder()
    {
        return false;
    }

    public Map<Token, Float> describeOwnership(List<Token> sortedTokens)
    {
        Map<Token, Float> ownerships = new HashMap<Token, Float>();
        Iterator<Token> i = sortedTokens.iterator();

        // 0-case
        if (!i.hasNext())
            throw new RuntimeException("No nodes present in the cluster. Has this node finished starting up?");
        // 1-case
        if (sortedTokens.size() == 1)
            ownerships.put(i.next(), 1.0F);
        // n-case
        else
        {
            final BigInteger ri = BigInteger.valueOf(MAXIMUM).subtract(BigInteger.valueOf(MINIMUM.token + 1));  //  (used for addition later)
            final BigDecimal r  = new BigDecimal(ri);
            Token start = i.next();BigInteger ti = BigInteger.valueOf(((LongToken)start).token);  // The first token and its value
            Token t; BigInteger tim1 = ti;                                                                // The last token and its value (after loop)

            while (i.hasNext())
            {
                t = i.next(); ti = BigInteger.valueOf(((LongToken) t).token); // The next token and its value
                float age = new BigDecimal(ti.subtract(tim1).add(ri).mod(ri)).divide(r, 6, BigDecimal.ROUND_HALF_EVEN).floatValue(); // %age = ((T(i) - T(i-1) + R) % R) / R
                ownerships.put(t, age);                           // save (T(i) -> %age)
                tim1 = ti;                                        // -> advance loop
            }

View on GitHub (pinned to 88fd0f6a0e)