Skip to content

fix(native): zstd compression fails with NoSuchFieldError in the native image - #501

Merged
bmscomp merged 1 commit into
mainfrom
fix/native-zstd-jni-fields
Oct 25, 2026
Merged

bmscomp merged 1 commit into
mainfrom
fix/native-zstd-jni-fields

Conversation

@bmscomp

@bmscomp bmscomp commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Problem

On the native image every benchmark using compression=zstd ends FAILED. Each producer dies on its first batch:

java.lang.NoSuchFieldError: com.github.luben.zstd.ZstdOutputStreamNoFinalizer.dstPos
  at ZstdOutputStreamNoFinalizer.resetCStream → close → MemoryRecordsBuilder.closeForRecordAppends → KafkaProducer.send

zstd-jni's native code reads and writes Java fields (srcPos, dstPos, nativePtr, ...) through GetFieldID. The native build kept the methods but not those fields. lz4 and snappy are unaffected: their native code never touches fields.

Fix

  • jni-config.json registers allDeclaredFields on every zstd class, and adds BaseZstdBufferDecompressingStreamNoFinalizer, which holds the inherited consumed/produced fields.
  • The file moves from META-INF/native-image/ to META-INF/native-image/com.bmscomp/kates/. At the root the native build never read it, so registering the fields alone changed nothing. A rebuild with only that change still threw the same error.
  • scripts/native-e2e.sh runs its 10k-record LOAD test with both lz4 and zstd.

Verification

Built kates:native-local from this branch on the panda kind cluster (SCRAM Kafka krafter) and deployed it as a throwaway kates-e2e release:

Codec Run Result
zstd b8f1b243 DONE, ~43K rec/s
lz4 771b7686 DONE

The pod logged no NoSuchFieldError. Before the move, the same zstd test failed with it.

The full make native-e2e script did not get as far as the LOAD test on that cluster. The chart's helm test fails earlier on /q/openapi -> 401, which is unrelated to this change, so I ran the script's LOAD commands by hand.

Not in this PR

  • reflect-config.json and resource-config.json sit in the same unread location, so they have never applied either. That explains the note in CompressionReflectionConfig that reflect-config.json "did not take". Moving them changes what goes into the build, so it's left for a separate change.
  • On failure, native-e2e.sh prints logs from the postgres pod instead of kates, because its label selector matches both.

…ve image

zstd-jni's native code reads and writes Java fields (srcPos, dstPos,
nativePtr, ...) through GetFieldID. jni-config.json registered only the
methods, so every zstd producer died on its first batch with
NoSuchFieldError: ZstdOutputStreamNoFinalizer.dstPos.

Registering the fields was not enough on its own: the file sat at the
root of META-INF/native-image/, where the native build never reads it.
It now lives under META-INF/native-image/com.bmscomp/kates/.

native-e2e.sh runs its LOAD test with zstd as well as lz4, so a codec
that only breaks in the native image fails the script.
@bmscomp
bmscomp merged commit 235a9b5 into main Oct 25, 2026
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant