{"id":"98ba4e2c345389cd","repo":"apache/kafka","slug":"provided-null-buffer","errorCode":null,"errorMessage":"provided null buffer","messagePattern":"provided null buffer","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/memory/SimpleMemoryPool.java","lineNumber":91,"sourceCode":"        if (success) {\n            maybeRecordEndOfDrySpell();\n        } else {\n            if (oomTimeSensor != null) {\n                startOfNoMemPeriod.compareAndSet(0, System.nanoTime());\n            }\n            log.trace(\"refused to allocate buffer of size {}\", sizeBytes);\n            return null;\n        }\n\n        ByteBuffer allocated = ByteBuffer.allocate(sizeBytes);\n        bufferToBeReturned(allocated);\n        return allocated;\n    }\n\n    @Override\n    public void release(ByteBuffer previouslyAllocated) {\n        if (previouslyAllocated == null)\n            throw new IllegalArgumentException(\"provided null buffer\");\n\n        bufferToBeReleased(previouslyAllocated);\n        availableMemory.addAndGet(previouslyAllocated.capacity());\n        maybeRecordEndOfDrySpell();\n    }\n\n    @Override\n    public long size() {\n        return sizeBytes;\n    }\n\n    @Override\n    public long availableMemory() {\n        return availableMemory.get();\n    }\n\n    @Override\n    public boolean isOutOfMemory() {","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/memory/SimpleMemoryPool.java#L73-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — finally releases unconditionally\nByteBuffer buf = pool.tryAllocate(size);\ntry {\n    write(buf);\n} finally {\n    pool.release(buf);  // throws if tryAllocate returned null when pool was empty\n}\n\n// after — release only when allocation actually succeeded\nByteBuffer buf = pool.tryAllocate(size);\nif (buf == null) throw new BufferExhaustedException(\"pool exhausted for size \" + size);\ntry {\n    write(buf);\n} finally {\n    pool.release(buf);\n}","handlingStrategy":"type-guard","validationCode":"// Only release buffers you actually got from tryAllocate.\nByteBuffer buf = ...;\nif (buf == null) {\n    // skip silently — releasing null is a programming error in the pool's contract.\n    return;\n}\npool.release(buf);","typeGuard":"// Track 'buffer came from the pool' as a non-null type so null releases are impossible.\nstatic void releaseIfPresent(MemoryPool pool, ByteBuffer buf) {\n    if (buf != null) pool.release(buf);\n}","tryCatchPattern":"try {\n    pool.release(buf);\n} catch (IllegalArgumentException e) {\n    // a null release is a coding bug — log loudly and audit the release path.\n    log.error(\"Attempted to release a null buffer to the pool\", e);\n}","preventionTips":["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."],"tags":["kafka-clients","memory-pool","null-handling","resource-lifecycle"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}