aeron-io/aeron · error · ControlProtocolException
UNKNOWN_COUNTER
UNKNOWN_COUNTER
Error message
unknown counter: ${registrationId} What it means
Thrown by DriverConductor when a removeCounter command references a counter registrationId that has no matching CounterLink in the driver. The driver looks up the counter among its active links; if none matches it throws ControlProtocolException with code UNKNOWN_COUNTER, so the client's remove operation fails and no onUnavailableCounter/operationSucceeded is issued for it.
Solutions
- Pass counter.registrationId() (the long registration id), not the counterId int, to removeCounter
- Release each counter once; guard double-release paths by nulling the reference after release
- Catch ControlProtocolException with UNKNOWN_COUNTER and treat as already-released during cleanup
- Verify the counter still exists via CountersReader before removal (check its state is not RECORD_RECLAIMED)
Example fix
// before
long id = counter.counterId(); // wrong: int counterId, not registrationId
aeron.removeCounter(id);
// after
try {
aeron.removeCounter(counter.registrationId());
} catch (ControlProtocolException e) {
if (e.errorCode() == ControlProtocolException.Code.UNKNOWN_COUNTER) {
// counter already released
} else { throw e; }
} Defensive patterns
Strategy: try-catch
Validate before calling
final CountersReader counters = aeron.countersReader();
if (counters.getCounterState(counter.counterId()) != CountersReader.RECORD_ALLOCATED) { return; } Type guard
boolean isCounterLive(CountersReader r, Counter c) { return !c.isClosed() && r.getCounterState(c.counterId()) == CountersReader.RECORD_ALLOCATED; } Try / catch
try {
aeron.removeCounter(counter.registrationId());
} catch (ControlProtocolException e) {
if (e.errorCode() != ControlProtocolException.Code.UNKNOWN_COUNTER) throw e;
// already released
} Prevention
- Use registrationId() (long), never counterId() (int), for removal
- Null out counter references after release to prevent double-release
- Check counter state via CountersReader before releasing
- Release counters on the same client that allocated them
When it happens
Trigger: Calling CountersReader/ClientConductor releaseCounter or Aeron.removeCounter() with an id that was never allocated, already released, or whose counter was freed when its owning client was timed out by the driver.
Common situations: Double-releasing counters in shutdown code; using a counter id (int) instead of the registrationId (long); releasing after driver restart; counters closed automatically on client timeout then released again by app code.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- UNKNOWN_PUBLICATION
- unknown subscription
- registration id is not a Counter
- Driver events adapter is invalid
- no response from MediaDriver within
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/32492bdf7937cc64.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java:961
void onRemoveCounter(final long registrationId, final long correlationId)
{
CounterLink counterLink = null;
final ArrayList<CounterLink> counterLinks = this.counterLinks;
for (int i = 0, size = counterLinks.size(); i < size; i++)
{
final CounterLink link = counterLinks.get(i);
if (registrationId == link.registrationId())
{
counterLink = link;
fastUnorderedRemove(counterLinks, i);
break;
}
}
if (null == counterLink)
{
throw new ControlProtocolException(UNKNOWN_COUNTER, "unknown counter: " + registrationId);
}
clientProxy.operationSucceeded(correlationId);
clientProxy.onUnavailableCounter(registrationId, counterLink.counterId());
counterLink.close();
}
void onClientClose(final long clientId)
{
final AeronClient client = findClient(clients, clientId);
if (null != client)
{
client.onClosedByCommand();
}
}
void onAddRcvDestination(final long registrationId, final String destinationChannel, final long correlationId)
{View on GitHub (pinned to 6d60124e15)