aeron-io/aeron · error · ClusterException

error counter must be supplied if aeron client is

Error message

error counter must be supplied if aeron client is

What it means

If the Aeron client is externally supplied, conclude() requires an errorCounter (CountedErrorHandler needs it to track errors); when a client is provided but errorCounter is null, the ClusterException "error counter must be supplied if aeron client is" is thrown. When the module owns the client it allocates the counter itself, so this only fires for user-supplied clients.

Solutions

  1. Call ctx.errorCounter(...) with a counter allocated via ClusterCounters.allocateVersioned(...) (CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID)
  2. Or let the module create its own client and counter by not calling ctx.aeron(...)
  3. If the client is external, also supply the matching countedErrorHandler/error log wiring expected by the cluster

Example fix

// before
ctx.aeron(externalClient); // errorCounter not set
// after
Counter errorCounter = ClusterCounters.allocateVersioned(aeron, buffer,
    "Cluster Errors", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID, clusterId,
    ConsensusModuleVersion.VERSION, ConsensusModuleVersion.GIT_SHA);
ctx.aeron(externalClient).errorCounter(errorCounter);
Defensive patterns

Strategy: validation

Validate before calling

if (externalClientUsed && ctx.errorCounter() == null) {
    throw new IllegalStateException("errorCounter must be supplied when passing an external Aeron client");
}

Try / catch

try { ctx.conclude(); } catch (ClusterException e) { if (e.getMessage().contains("error counter must be supplied")) { /* allocate counter via ClusterCounters and retry setup */ } }

Prevention

When it happens

Trigger: ctx.aeron(client) set (or errorCounter otherwise null after the client-owned allocation path) and ctx.errorCounter(...) never called before conclude().

Common situations: Embedding the ConsensusModule with a shared Aeron client but forgetting cluster counters wiring; copying Context setup from an owned-client example and adding an external client without adding errorCounter.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java:1920

            if (null == ingressChannel)
            {
                throw new ClusterException("ingressChannel must be specified");
            }

            if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))
            {
                throw new ClusterException("Aeron client must use a RethrowingErrorHandler");
            }

            if (!aeron.context().useConductorAgentInvoker())
            {
                throw new ClusterException("Aeron client must use conductor agent invoker");
            }

            if (null == errorCounter)
            {
                throw new ClusterException("error counter must be supplied if aeron client is");
            }

            if (null == countedErrorHandler)
            {
                countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);
                if (ownsAeronClient)
                {
                    aeron.context().errorHandler(countedErrorHandler);
                }
            }

            if (null == moduleStateCounter)
            {
                final CountersReader counters = aeron.countersReader();
                if (Aeron.NULL_VALUE != ClusterCounters.find(counters, CONSENSUS_MODULE_STATE_TYPE_ID, clusterId))
                {
                    throw new ClusterException("existing consensus module detected for clusterId=" + clusterId);
                }

View on GitHub (pinned to 6d60124e15)