apache/kafka · warning · IllegalArgumentException

TransactionalId `{transactionalId}` was not included in the

Error message

TransactionalId `{transactionalId}` was not included in the request

What it means

Thrown by FenceProducersResult.findAndApply (used by producerId/epochId) when the caller asks for the fencing result of a transactionalId that was not part of the original Admin.fenceProducers request. The result resolves IDs to CoordinatorKey; a missing key means the producer was never requested, and lookup fails fast with IllegalArgumentException rather than returning a null future.

Source

Thrown at clients/src/main/java/org/apache/kafka/clients/admin/FenceProducersResult.java:77

    /**
     * Returns a future that provides the epoch ID generated while initializing the given transaction when the request completes.
     */
    public KafkaFuture<Short> epochId(String transactionalId) {
        return findAndApply(transactionalId, p -> p.epoch);
    }

    /**
     * Return a future which succeeds only if all the producer fencings succeed.
     */
    public KafkaFuture<Void> all() {
        return KafkaFuture.allOf(futures.values().toArray(new KafkaFuture<?>[0]));
    }

    private <T> KafkaFuture<T> findAndApply(String transactionalId, KafkaFuture.BaseFunction<ProducerIdAndEpoch, T> followup) {
        CoordinatorKey key = CoordinatorKey.byTransactionalId(transactionalId);
        KafkaFuture<ProducerIdAndEpoch> future = futures.get(key);
        if (future == null) {
            throw new IllegalArgumentException("TransactionalId " +
                "`" + transactionalId + "` was not included in the request");
        }
        return future.thenApply(followup);
    }
}

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Pass the exact transactional ID string that you included in the fenceProducers request.
  2. Iterate the original requested collection rather than constructing new IDs for lookup.
  3. Use fencedProducers() to get the full map of transactional ID to future instead of querying individual IDs.
  4. If the ID set changed, issue a new fenceProducers call with the complete list.

Example fix

// before
Collection<String> req = List.of("tx-1");
FenceProducersResult r = admin.fenceProducers(req);
r.producerId("tx-2").get(); // not in request

// after
for (String id : req) {
    r.producerId(id).get();
}
// or use the bulk accessor
Map<String, KafkaFuture<Void>> all = r.fencedProducers();
Defensive patterns

Strategy: validation

Validate before calling

// Keep the collection of transactional IDs sent to fenceProducers and only call
// producerId(...)/epochId(...) for those.
Collection<String> requestedIds = List.of("tx-1", "tx-2");
FenceProducersResult result = admin.fenceProducers(requestedIds);

String txId = "tx-1";
if (requestedIds.contains(txId)) {
    long pid = result.producerId(txId).get();
} else {
    log.warn("{} not in fenceProducers request", txId);
}

Try / catch

try {
    long pid = result.producerId(txId).get();
} catch (IllegalArgumentException e) {
    // `txId` was not in the fenceProducers request. Resubmit or drop.
    log.warn("Cannot fence {}: {}", txId, e.getMessage());
}

Prevention

When it happens

Trigger: Calling result.producerId(txnId) or result.epochId(txnId) where txnId was not in the Collection<String> passed to Admin.fenceProducers. CoordinatorKey.byTransactionalId produces a key not present in the futures map, so future == null triggers the exception.

Common situations: Caller fences a subset of producers but queries a different one (typo, stale config, env mismatch); dynamic transactional ID generation for lookup vs static request set; cross-thread sharing of a result object; refactored code that changed the fenced producer list without updating queries.

Related errors


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/aa006b4f539e16e3.json. Report an issue: GitHub.