apache/kafka · error · IllegalStateException

MockProducer is already closed.

Error message

MockProducer is already closed.

What it means

Thrown by MockProducer.verifyNotClosed() as an IllegalStateException whenever any transactional or lifecycle operation is invoked after the producer has been closed via close() or close(Duration). MockProducer tracks a `closed` flag that close() sets to true, and every transactional method guards itself by calling verifyNotClosed() first. It exists to mirror the real KafkaProducer behavior so tests can assert that their code correctly stops using a producer after teardown.

Source

Thrown at clients/src/main/java/org/apache/kafka/clients/producer/MockProducer.java:294

        
        if (!this.transactionInFlight) {
            throw new IllegalStateException("There is no prepared transaction to complete.");
        }

        // For testing purposes, we'll consider a prepared state with producerId=1000L and epoch=1 as valid
        // This should match what's returned in prepareTransaction()
        PreparedTxnState currentState = new PreparedTxnState(1000L, (short) 1);
        
        if (currentState.equals(preparedTxnState)) {
            commitTransaction();
        } else {
            abortTransaction();
        }
    }

    private synchronized void verifyNotClosed() {
        if (this.closed) {
            throw new IllegalStateException("MockProducer is already closed.");
        }
    }

    private synchronized void verifyNotFenced() {
        if (this.producerFenced) {
            throw new ProducerFencedException("MockProducer is fenced.");
        }
    }

    private void verifyTransactionsInitialized() {
        if (!this.transactionInitialized) {
            throw new IllegalStateException("MockProducer hasn't been initialized for transactions.");
        }
    }

    private void verifyTransactionInFlight() {
        if (!this.transactionInFlight) {
            throw new IllegalStateException("There is no open transaction.");

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Inspect the stack trace to find which method was called after close(); reorder so close() is the last operation on that instance.
  2. If using a shared field, create a fresh MockProducer per test method (e.g. in @BeforeEach) instead of reusing one that a previous test closed.
  3. If the production code legitimately retries after close, fix the production logic — a real KafkaProducer behaves the same way and the bug would surface in production too.
  4. In teardown, set the producer reference to null after close() so accidental reuse fails fast with an NPE rather than a misleading state error.

Example fix

// before
producer.close();
producer.commitTransaction(); // IllegalStateException

// after
producer.commitTransaction();
producer.close();
Defensive patterns

Strategy: try-catch

Try / catch

try {
    producer.completeTransaction(prepared);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("already closed")) {
        // producer was closed earlier in the flow; abandon the call
        // optionally recreate a new MockProducer if still needed
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling initTransactions(boolean), beginTransaction(), sendOffsetsToTransaction(...), prepareTransaction(), commitTransaction(), abortTransaction(), completeTransaction(...), flush(), or fenceProducer() on a MockProducer instance after close()/close(Duration) has already been invoked on it. The guard is in verifyNotClosed() at line 292-296 and is reached synchronously on the calling thread.

Common situations: Test @AfterEach / tearDown methods that close the mock producer while async or later test steps still reference it; a shared mock producer field being closed by one test method and reused by another in a non-isolated test class; refactoring a production wrapper class that closes the producer in finally/try-with-resources but test code continues calling it.

Related errors


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