risingwavelabs/risingwave · error · BindingError::Jni
JniError {error}
Error message
JniError {error} What it means
BindingError::Jni is the variant of the risingwave_jni_core BindingError enum that wraps any jni::errors::Error produced while calling into the JNI API from Rust (e.g. env.get_string, array access, class lookups). The jni crate returns these errors instead of panicking when a JNI call fails, such as when a passed Java object handle is invalid, a type is wrong, or an exception is pending on the thread. The BindingError is converted by execute_and_catch into a thrown Java exception on the calling side.
Source
Thrown at src/jni_core/src/lib.rs:84
use tokio::sync::mpsc::{Receiver, Sender};
use tracing_slf4j::*;
/// Enable JVM and Java libraries.
///
/// This macro forces this crate to be linked, which registers the JVM builder.
#[macro_export]
macro_rules! enable {
() => {
use risingwave_jni_core as _;
};
}
pub static JAVA_BINDING_ASYNC_RUNTIME: LazyLock<Runtime> =
LazyLock::new(|| tokio::runtime::Runtime::new().unwrap());
#[derive(Error, Debug)]
pub enum BindingError {
#[error("JniError {error}")]
Jni {
#[from]
error: jni::errors::Error,
backtrace: Backtrace,
},
#[error("StorageError {error}")]
Storage {
#[from]
error: anyhow::Error,
backtrace: Backtrace,
},
#[error("DecodeError {error}")]
Decode {
#[from]
error: DecodeError,
backtrace: Backtrace,View on GitHub (pinned to 6469eb736d)
Solutions
- Check the Java call site for null or wrongly-typed arguments before invoking the native binding; the message names the wrapped jni::errors::Error which identifies the exact failing JNI operation
- If the error is JavaException, inspect the pending Java exception on the thread — it was thrown by Java code invoked from Rust and must be handled/cleared there
- Rebuild/verify JVM and binding versions match: stale JAR/so combinations cause JniError::MethodNotFound/ClassNotFound
- Wrap the JNI call site with env.exception_check()/exception_clear() diagnostics to find the pending exception
Example fix
// before
let msg: String = env.get_string(&jstr)?.into(); // panics/throws on null jstr
// after
if jstr.is_null() {
return Err(BindingError::Jni { error: jni::errors::Error::NullPtr, backtrace: Backtrace::capture() });
}
let msg: String = env.get_string(&jstr)?.into(); Defensive patterns
Strategy: try-catch
Validate before calling
// Java, before calling any binding method
if (jstr == null) throw new IllegalArgumentException("binding argument must not be null"); Type guard
// Java
static boolean isValidJStringArg(Object arg) {
return arg instanceof String && !((String) arg).isEmpty();
} Try / catch
try {
Binding.someNativeMethod(arg);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("JniError")) {
// inspect wrapped jni::errors::Error report, log and rethrow as domain error
}
throw e;
} Prevention
- Null-check every argument on the Java side before native calls
- Keep the JAR and native library versions in lockstep
- Clear pending Java exceptions before re-entering native code
- Never pass stale local references across JNI calls
When it happens
Trigger: Any JNI entry point in the binding (string extraction via env.get_string, byte-array access via to_guarded_slice/get_array_elements, JObject casts) receives an invalid, null, or wrong-typed Java object, or a Java exception is already pending when the native code makes another JNI call.
Common situations: Java side passes null where a String or byte[] is expected; calling a binding after the JVM changed the type of a field; a previously thrown Java exception was not cleared before re-entering native code; stale local references used across calls.
Related errors
- Failed to list iceberg namespaces.
- Failed to create namespace.
- Failed to check namespace exists.
- Failed to list iceberg tables.
- Failed to create iceberg table.
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/f177c32ca417ca96.
Report an issue: GitHub.