aeron-io/aeron · error · ClusterException

serviceId= invalid for serviceCount=

Error message

serviceId=<serviceId> invalid for serviceCount=<serviceCount>

What it means

When loading service message tracker state from a snapshot, the serviceId index must fall within the configured serviceCount (pendingServiceMessageTrackers array length). A serviceId outside [0, serviceCount) indicates the snapshot was produced by a cluster with more services than this node is configured for.

Solutions

  1. Set the cluster's serviceCount to match the snapshot (the service count that produced it).
  2. Take a fresh snapshot with the reduced service count before removing services.
  3. Regenerate/rebuild cluster state if services were removed and no compatible snapshot exists.
  4. Validate snapshot contents when migrating state between clusters with different service topologies.

Example fix

// before
ctx.serviceCount(2); // snapshot has 3 services
// after
ctx.serviceCount(3); // matches snapshot, then snapshot again after removing a service
Defensive patterns

Strategy: validation

Validate before calling

// before load
if (configuredServiceCount < snapshotServiceCount) {
    throw new IllegalStateException("serviceCount " + configuredServiceCount + " smaller than snapshot's " + snapshotServiceCount);
}

Try / catch

try { cluster.start(); } catch (ClusterException e) { if (e.getMessage().contains("invalid for serviceCount")) { raiseServiceCountOrRestoreSnapshot(); } else { throw e; } }

Prevention

When it happens

Trigger: onServiceMessageTracker loading state where serviceId < 0 or >= pendingServiceMessageTrackers.length, typically because the snapshot contains entries for services that no longer exist under the current serviceCount configuration.

Common situations: Reducing the number of clustered services without regenerating the snapshot; a snapshot produced with a different aeron.cluster.service.count; corrupted or hand-edited snapshot data.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/6ae36a768a4d8dd5. Report an issue: GitHub.

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAgent.java:729

        if (pendingServiceMessageTrackers.length > 0)
        {
            pendingServiceMessageTrackers[0].loadState(
                nextServiceSessionId, logServiceSessionId, pendingMessageCapacity);
        }
    }

    public void onLoadPendingMessageTracker(
        final long nextServiceSessionId,
        final long logServiceSessionId,
        final int pendingMessageCapacity,
        final int serviceId,
        final DirectBuffer buffer,
        final int offset,
        final int length)
    {
        if (serviceId < 0 || serviceId >= pendingServiceMessageTrackers.length)
        {
            throw new ClusterException(
                "serviceId=" + serviceId + " invalid for serviceCount=" + pendingServiceMessageTrackers.length);
        }

        pendingServiceMessageTrackers[serviceId].loadState(
            nextServiceSessionId, logServiceSessionId, pendingMessageCapacity);
    }

    public void onLoadPendingMessage(
        final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)
    {
        final int serviceId = PendingServiceMessageTracker.serviceIdFromLogMessage(clusterSessionId);
        pendingServiceMessageTrackers[serviceId].appendMessage(buffer, offset, length);
    }

    public void onLoadTimer(
        final long correlationId, final long deadline, final DirectBuffer buffer, final int offset, final int length)
    {
        onScheduleTimer(correlationId, deadline);

View on GitHub (pinned to 6d60124e15)