apache/kafka · error · IllegalArgumentException
provided null buffer
Error message
provided null buffer
What it means
Thrown by SimpleMemoryPool.release(ByteBuffer) when null is passed in. Returning a null buffer breaks the pool's bookkeeping because release() reads previouslyAllocated.capacity() to credit memory back to the AtomicLong; a null buffer would NPE there. The guard rejects null early with a clear IllegalArgumentException instead.
Source
Thrown at clients/src/main/java/org/apache/kafka/common/memory/SimpleMemoryPool.java:91
if (success) {
maybeRecordEndOfDrySpell();
} else {
if (oomTimeSensor != null) {
startOfNoMemPeriod.compareAndSet(0, System.nanoTime());
}
log.trace("refused to allocate buffer of size {}", sizeBytes);
return null;
}
ByteBuffer allocated = ByteBuffer.allocate(sizeBytes);
bufferToBeReturned(allocated);
return allocated;
}
@Override
public void release(ByteBuffer previouslyAllocated) {
if (previouslyAllocated == null)
throw new IllegalArgumentException("provided null buffer");
bufferToBeReleased(previouslyAllocated);
availableMemory.addAndGet(previouslyAllocated.capacity());
maybeRecordEndOfDrySpell();
}
@Override
public long size() {
return sizeBytes;
}
@Override
public long availableMemory() {
return availableMemory.get();
}
@Override
public boolean isOutOfMemory() {View on GitHub (pinned to c31c9215e1)
Solutions
- Check the message — it almost always means tryAllocate returned null (pool exhausted) and the finally block blindly called release(null).
- Guard the release: `if (buf != null) pool.release(buf);` so exhausted-pool paths don't try to return a null buffer.
- If the release site is in your own code, audit every allocation point and pair it with a conditional release in finally.
- If you are seeing this from inside the Kafka producer itself, file a bug — internal call sites already guard against null; this indicates a version-specific regression.
- Add a unit test that exercises the pool-exhausted path (allocate until tryAllocate returns null, then trigger the cleanup hook) to confirm the fix.
Example fix
// before — finally releases unconditionally
ByteBuffer buf = pool.tryAllocate(size);
try {
write(buf);
} finally {
pool.release(buf); // throws if tryAllocate returned null when pool was empty
}
// after — release only when allocation actually succeeded
ByteBuffer buf = pool.tryAllocate(size);
if (buf == null) throw new BufferExhaustedException("pool exhausted for size " + size);
try {
write(buf);
} finally {
pool.release(buf);
} Defensive patterns
Strategy: type-guard
Validate before calling
// Only release buffers you actually got from tryAllocate.
ByteBuffer buf = ...;
if (buf == null) {
// skip silently — releasing null is a programming error in the pool's contract.
return;
}
pool.release(buf); Type guard
// Track 'buffer came from the pool' as a non-null type so null releases are impossible.
static void releaseIfPresent(MemoryPool pool, ByteBuffer buf) {
if (buf != null) pool.release(buf);
} Try / catch
try {
pool.release(buf);
} catch (IllegalArgumentException e) {
// a null release is a coding bug — log loudly and audit the release path.
log.error("Attempted to release a null buffer to the pool", e);
} Prevention
- Pair every tryAllocate with exactly one release in a try/finally; never release the same buffer twice or null.
- Use Optional<ByteBuffer> or a sentinel-tracking wrapper so a missing allocation cannot reach release().
- If you return early from a path that did not allocate, skip release rather than passing null.
- Consider try-with-resources around a wrapper that releases the buffer in close() only when it was successfully allocated.
When it happens
Trigger: A caller hands a previously-allocated buffer back to the pool but the local reference is null — e.g. a try/finally that releases the buffer even though tryAllocate returned null (the pool returns null when exhausted in non-blocking mode), or a code path that nulls out the reference after use and then calls release in a finally block.
Common situations: Finally block that unconditionally calls pool.release(buf) where buf was assigned from a tryAllocate that legitimately returned null under backpressure; a producer/sender code path that clears its buffer reference on send-completion but the cleanup hook still runs; refactoring that introduced an early `buf = null;` before release; test code reusing a null buffer mock.
Related errors
- failed closing plugin
- must provide a positive size and max single allocation size
- requested size {sizeBytes}<=0
- requested size {sizeBytes} is larger than maxSingleAllocatio
- Block checksum mismatch
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/98ba4e2c345389cd.json.
Report an issue: GitHub.