{"id":"32fdce6c90a8008a","repo":"apache/kafka","slug":"mark-not-supported","errorCode":null,"errorMessage":"mark not supported","messagePattern":"mark not supported","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"clients/src/main/java/org/apache/kafka/common/compress/Lz4BlockInputStream.java","lineNumber":271,"sourceCode":"        }\n        int skipped = (int) Math.min(n, available());\n        decompressedBuffer.position(decompressedBuffer.position() + skipped);\n        return skipped;\n    }\n\n    @Override\n    public int available() {\n        return decompressedBuffer == null ? 0 : decompressedBuffer.remaining();\n    }\n\n    @Override\n    public void close() {\n        bufferSupplier.release(decompressionBuffer);\n    }\n\n    @Override\n    public void mark(int readlimit) {\n        throw new RuntimeException(\"mark not supported\");\n    }\n\n    @Override\n    public void reset() {\n        throw new RuntimeException(\"reset not supported\");\n    }\n\n    /**\n     * Checks whether the version of lz4 on the classpath has the fix for reading from ByteBuffers with\n     * non-zero array offsets (see https://github.com/lz4/lz4-java/pull/65)\n     */\n    static void detectBrokenLz4Version() {\n        byte[] source = new byte[]{1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3};\n        final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor();\n\n        final byte[] compressed = new byte[compressor.maxCompressedLength(source.length)];\n        final int compressedLength = compressor.compress(source, 0, source.length, compressed, 0,\n                                                         compressed.length);","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/compress/Lz4BlockInputStream.java#L253-L289","documentation":"mark(int) is overridden in Lz4BlockInputStream to unconditionally throw RuntimeException because the stream consumes and decompresses from a forward-only ByteBuffer and cannot snapshot a rewind point. This is a deliberate API contract: the class does not support mark/reset semantics inherited from java.io.InputStream.","triggerScenarios":"Any code calling Lz4BlockInputStream.mark(readLimit) — typically a wrapper utility, a third-party library (e.g. some IOHelpers, Apache Commons IO, certain serialization frameworks), or generated code that probes markSupported() incorrectly and then calls mark(). Triggered at line 271.","commonSituations":"Using a generic InputStream wrapper that assumes mark/reset (e.g. some XML/JSON parsers, java.io.BufferedInputStream-style helpers) on top of a Kafka LZ4 stream; old code migrated from a different decompressor that did support mark; testing utilities that mark every stream defensively. Less common with direct Kafka consumers, more common in Connect transforms or Streams custom serializers.","solutions":["Remove the mark() call — Lz4BlockInputStream is forward-only by design; buffer the decompressed bytes upstream if you need re-read.","If re-read is genuinely required, fully drain the stream into a ByteArrayOutputStream / ByteBuffer first, then replay that buffer.","Guard callers with an explicit markSupported() check (it inherits false from InputStream) before invoking mark()."],"exampleFix":"// before\nInputStream decompressed = new Lz4BlockInputStream(buf, supplier, true);\ndecompressed.mark(1024);\n...\ndecompressed.reset();\n\n// after: buffer the whole frame if you need re-read\nByteArrayOutputStream drained = new ByteArrayOutputStream();\ntry (InputStream in = new Lz4BlockInputStream(buf, supplier, true)) {\n    in.transferTo(drained);\n}\nbyte[] replayable = drained.toByteArray();","handlingStrategy":"validation","validationCode":"// Lz4BlockInputStream.markSupported() is hardcoded false.\n// Guard every call site that reaches for mark().\nInputStream in = ...;\nif (in.markSupported()) {\n    in.mark(readlimit);\n} else {\n    // buffer externally, or re-open the source\n}","typeGuard":"// Narrow on the capability, not the class — works for any non-markable stream.\nstatic boolean canMark(InputStream in) {\n    return in != null && in.markSupported();\n}","tryCatchPattern":"try {\n    in.mark(readlimit);\n} catch (RuntimeException e) {\n    if (\"mark not supported\".equals(e.getMessage())) {\n        // Fall back to buffered read-into-byte[] and replay from memory.\n    } else throw e;\n}","preventionTips":["Never call mark() on a stream whose contract forbids it; Lz4BlockInputStream documents mark/reset as unsupported.","If you need replay semantics, wrap the decompressed bytes in a ByteArrayInputStream or PushbackInputStream after fully draining the LZ4 stream.","Prefer try-with-resources and a single forward-only pass over decompressed records — Kafka record batches are designed for sequential consumption.","Lint for mark()/reset() calls on InputStream references of unknown provenance; gate them behind a markSupported() check."],"tags":["compression","lz4","io","api-misuse","kafka-clients"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}