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
- Wait for the node to finish startup and join the ring (nodetool status shows the node UP) before querying ownership.
- Pass a non-empty sortedTokens collection when calling describeOwnership programmatically.
- In tests, seed TokenMetadata with at least one token before calling describeOwnership.
- 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
- Wait for nodetool status to show the node UP/normal before ownership queries.
- Add retry/backoff in monitoring scripts around startup.
- In tests, seed TokenMetadata with tokens before asserting ownership.
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
- Missing directive: partitioner
- 3
- accord.journal_directory must not be the same as the…
- accord.working_set_size option was set incorrectly to
- Attempted to delete an element from a list which is null
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)