{"record":{"id":"a0bc7228ddc7380d","repo":"aeron-io/aeron","slug":"error-counter-must-be-supplied-if-aeron-client-is-a0bc72","errorCode":null,"errorMessage":"error counter must be supplied if aeron client is","messagePattern":"error counter must be supplied if aeron client is","errorType":"validation","errorClass":"ClusterException","httpStatus":null,"severity":"error","filePath":"aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java","lineNumber":1920,"sourceCode":"\n            if (null == ingressChannel)\n            {\n                throw new ClusterException(\"ingressChannel must be specified\");\n            }\n\n            if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))\n            {\n                throw new ClusterException(\"Aeron client must use a RethrowingErrorHandler\");\n            }\n\n            if (!aeron.context().useConductorAgentInvoker())\n            {\n                throw new ClusterException(\"Aeron client must use conductor agent invoker\");\n            }\n\n            if (null == errorCounter)\n            {\n                throw new ClusterException(\"error counter must be supplied if aeron client is\");\n            }\n\n            if (null == countedErrorHandler)\n            {\n                countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);\n                if (ownsAeronClient)\n                {\n                    aeron.context().errorHandler(countedErrorHandler);\n                }\n            }\n\n            if (null == moduleStateCounter)\n            {\n                final CountersReader counters = aeron.countersReader();\n                if (Aeron.NULL_VALUE != ClusterCounters.find(counters, CONSENSUS_MODULE_STATE_TYPE_ID, clusterId))\n                {\n                    throw new ClusterException(\"existing consensus module detected for clusterId=\" + clusterId);\n                }","sourceCodeStart":1902,"sourceCodeEnd":1938,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java#L1902-L1938","documentation":"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.","triggerScenarios":"ctx.aeron(client) set (or errorCounter otherwise null after the client-owned allocation path) and ctx.errorCounter(...) never called before conclude().","commonSituations":"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.","solutions":["Call ctx.errorCounter(...) with a counter allocated via ClusterCounters.allocateVersioned(...) (CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID)","Or let the module create its own client and counter by not calling ctx.aeron(...)","If the client is external, also supply the matching countedErrorHandler/error log wiring expected by the cluster"],"exampleFix":"// before\nctx.aeron(externalClient); // errorCounter not set\n// after\nCounter errorCounter = ClusterCounters.allocateVersioned(aeron, buffer,\n    \"Cluster Errors\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID, clusterId,\n    ConsensusModuleVersion.VERSION, ConsensusModuleVersion.GIT_SHA);\nctx.aeron(externalClient).errorCounter(errorCounter);","handlingStrategy":"validation","validationCode":"if (externalClientUsed && ctx.errorCounter() == null) {\n    throw new IllegalStateException(\"errorCounter must be supplied when passing an external Aeron client\");\n}","typeGuard":null,"tryCatchPattern":"try { ctx.conclude(); } catch (ClusterException e) { if (e.getMessage().contains(\"error counter must be supplied\")) { /* allocate counter via ClusterCounters and retry setup */ } }","preventionTips":["When supplying ctx.aeron(...), also supply ctx.errorCounter(...) from ClusterCounters","Or skip both and let the ConsensusModule own client + counters","Keep counter allocation in a shared bootstrap helper so it is never forgotten"],"tags":["configuration","counters","aeron-client"],"backgroundTag":"missing-required-config-field","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}