Skip to main content

native_method

Macro native_method 

Source
native_method!() { /* proc-macro */ }
Expand description

Create a compile-time type-checked NativeMethod for registering native methods with the JVM.

This macro generates a NativeMethod struct with compile-time guarantees that the Rust function matches the JNI signature. It can optionally:

  • Wrap implementations with panic safety (catch_unwind) and error handling
  • Generate JNI export symbols for automatic JVM resolution
  • Perform runtime ABI checks to ensure static/instance methods are registered correctly

This macro provides strong type safety for implementing individual native methods.

§Quick Example

// Instance method with default settings
const ADD_METHOD: jni::NativeMethod = native_method! {
    java_type = "com.example.MyClass",
    extern fn native_add(a: jint, b: jint) -> jint,
};
// Will export `Java_com_example_MyClass_nativeAdd__II` symbol and
// `ADD_METHOD` can be passed to `Env::register_native_methods`

fn native_add<'local>(
    _env: &mut Env<'local>,
    _this: JObject<'local>,
    a: jint,
    b: jint,
) -> Result<jint, jni::errors::Error> {
    Ok(a + b)
}

§Syntax Overview

The macro supports both property-based and shorthand syntax, which can be combined:

native_method! {
    [java_type = "com.example.MyClass",]  // For exports (required with `extern` or `export = true`)
    [rust_type = CustomType,]             // Type for 'this' (default: JObject)
    [static] [raw] [extern] fn [RustType::]method_name(args) -> ret, // Shorthand signature
    [fn = implementation_fn,]             // Function path (default: RustType::method_name from shorthand)
    [... other properties ...]
}

§Generated Code

The macro generates a const block containing:

  1. A type-checked wrapper function
  2. An optional runtime type-check for the second parameter (to distinguish static vs instance methods)
  3. An optional export function with a mangled JNI name
  4. A NativeMethod struct created via NativeMethod::from_raw_parts

For non-raw methods with the default settings:

const {
    // Generated wrapper with panic safety and error handling
    extern "system" fn __native_method_wrapper<'local>(
        mut unowned_env: EnvUnowned<'local>,
        this: JObject<'local>,
        a: jint,
        b: jint,
    ) -> jint {
        // One-time ABI check: validates that 'this' is NOT a Class (i.e., instance method)
        static _ABI_CHECK: ::std::sync::Once = ::std::sync::Once::new();
        _ABI_CHECK.call_once(|| {
            // ... check that second parameter is not java.lang.Class ...
        });

        unowned_env
            .with_env(|env| {
                // Call your implementation
                native_add(env, this, a, b)
            })
            .resolve::<ThrowRuntimeExAndDefault>()
    }

    unsafe {
        NativeMethod::from_raw_parts(
            jni_str!("nativeAdd"),
            jni_str!("(II)I"),
            __native_method_wrapper as *mut c_void,
        )
    }
}

With export = true or extern qualifier, an additional export function is generated:

#[export_name = "Java_com_example_MyClass_nativeAdd__II"]
pub extern "system" fn __native_method_export<'local>(...) -> jint {
    __native_method_wrapper(...)
}

The export_name is mangled according to the JNI Specification, under “Design” -> “Resolving Native Method Names”

https://docs.oracle.com/en/java/javase/11/docs/specs/jni/design.html#resolving-native-method-names

§Property Reference

§java_type - Java Class Name

Required when using export = true or the extern qualifier.

The fully-qualified Java class name containing this native method.

java_type = "com.example.MyClass"

Can also be specified as dot-separated identifiers:

java_type = com.example.MyClass

See the ‘Java Object Types’ section in the jni_sig! documentation for details on how to specify Java types, including inner classes and default-package classes.

§rust_type - Custom Type for ‘this’ Parameter

For instance methods, specifies the Rust type for the this parameter. Defaults to JObject.

rust_type = MyCustomType

This type must implement jni::refs::Reference.

§Shorthand Signature

The shorthand syntax allows specifying method details in a function-like form:

[static] [raw] [extern] fn [RustType::]method_name(args) -> ret

Where:

  • static - Static method (receives class: JClass instead of this)
  • raw - No panic safety wrapper, receives EnvUnowned, returns value directly (not Result)
  • extern - Generate JNI export symbol (requires java_type)
  • RustType:: - If present, sets rust_type = RustType and defaults fn = RustType::method_name
  • method_name - Converted from snake_case to lowerCamelCase for the Java method name

and the args and ret specify the method signature using the syntax from jni_sig!.

Example:

const METHOD: jni::NativeMethod = native_method! {
    static fn MyType::compute_sum(a: jint, b: jint) -> jint,
};

impl MyType<'_> {
    fn compute_sum<'local>(
        _env: &mut Env<'local>,
        _class: jni::objects::JClass<'local>,
        a: jint,
        b: jint,
    ) -> Result<jint, jni::errors::Error> {
        Ok(a + b)
    }
}

§fn - Implementation Function Path

Path to the Rust function implementing this native method. Defaults to RustType::method_name or method_name if a shorthand signature is given.

fn = my_module::my_implementation

§name - Java Method Name

The Java method name as a string literal. Defaults to the method_name name converted from snake_case to lowerCamelCase if a shorthand signature is given.

name = "customMethodName"

§sig / Method Signature

Typically the signature will come from the shorthand syntax, but it can also be specified explicitly via the sig property.

The method signature using the syntax from jni_sig!.

sig = (param1: jint, param2: JString) -> jboolean
// or shorthand (part of function-like syntax):
fn my_method(param1: jint, param2: JString) -> jboolean

§type_map - Type Mappings

Optional type mappings for custom Rust types. See jni_sig! for full syntax.

type_map = {
    CustomType => com.example.CustomClass,
    unsafe HandleType => long,
}

§static - Static Method Flag

Indicates a static method. The second parameter will be class: JClass instead of a this object.

static = true
// or as qualifier:
static fn my_method() -> jint

§raw - Raw Function Flag

When raw = true or the raw qualifier is used:

  • Function receives EnvUnowned<'local> (not &mut Env<'local>)
  • Function returns the value directly (not Result)
  • No panic safety wrapper (catch_unwind)
  • No automatic error handling
raw = true
// or as qualifier:
raw fn my_method(value: jint) -> jint

Raw function signature:

fn my_raw_method<'local>(
    env: EnvUnowned<'local>,
    this: JObject<'local>,
    value: jint,
) -> jint {
    value * 2
}

§export - JNI Export Symbol

Controls whether a JNI export symbol is generated:

  • true - Generate auto-mangled JNI export name (e.g., Java_com_example_Class_method__II)
  • false - Don’t generate export
  • "CustomName" - Use custom export name

Specifying the extern qualifier is equivalent to export = true.

Note: java_type must be provided when export = true or the extern qualifier is used.

export = true
// or as qualifier:
extern fn my_method() -> jint
// or with custom name:
export = "Java_custom_Name"

§error_policy - Error Handling Policy

For non-raw methods, specifies how to convert Result errors to JNI exceptions. Default is ThrowRuntimeExAndDefault.

Built-in policies:

  • jni::errors::ThrowRuntimeExAndDefault - Throws RuntimeException, returns default value
  • jni::errors::LogErrorAndDefault - Logs error, returns default value

Or implement your own policy by implementing the jni::errors::ErrorPolicy trait.

error_policy = jni::errors::LogErrorAndDefault

§catch_unwind - Panic Safety

For non-raw methods, controls whether panics are caught and converted to Java exceptions. Default is true.

  • true - Use EnvUnowned::with_env (catches panics)
  • false - Use EnvUnowned::with_env_no_catch (panics will abort when crossing FFI boundary)
catch_unwind = false

Note: Not applicable to raw methods (which never have panic safety).

§abi_check - Runtime ABI Validation

Controls runtime validation that the method is registered correctly as static/instance.

Values:

  • Always - Always check (default)
  • UnsafeNever - Never check (unsafe micro-optimization, for production if needed)
  • UnsafeDebugOnly - Check only in debug builds (unsafe micro-optimization, for production if needed)
abi_check = Always

The check validates that the second parameter (this for instance, class for static) matches how Java called the method. This is performed once per method via std::sync::Once.

Check failures for non-raw methods will throw an error that will be mapped via the specified error handling policy. For raw methods, a panic will occur, which will abort at the FFI boundary.

§jni - Override JNI Crate Path

Override the path to the jni crate. Must be the first property if provided.

jni = ::my_jni_crate

§Function Signature Requirements

§Non-raw (Default)

Instance method:

fn<'local>(
    env: &mut Env<'local>,
    this: RustType<'local>,  // Or JObject<'local>
    param1: jint,
    param2: JString<'local>,
    ...
) -> Result<ReturnType, E>
where E: Into<jni::errors::Error>

Static method:

fn<'local>(
    env: &mut Env<'local>,
    class: JClass<'local>,
    param1: jint,
    ...
) -> Result<ReturnType, E>
where E: Into<jni::errors::Error>

§Raw

Instance method:

fn<'local>(
    env: EnvUnowned<'local>,
    this: RustType<'local>,  // Or JObject<'local>
    param1: jint,
    ...
) -> ReturnType

Static method:

fn<'local>(
    env: EnvUnowned<'local>,
    class: JClass<'local>,
    param1: jint,
    ...
) -> ReturnType

§Complete Examples

§Basic Static Method

const METHOD: jni::NativeMethod = native_method! {
    static fn native_compute(value: jint) -> jint,
};

fn native_compute<'local>(
    _env: &mut Env<'local>,
    _class: JClass<'local>,
    value: jint,
) -> Result<jint, jni::errors::Error> {
    Ok(value * 100)
}

§Instance Method with Custom Type

const METHOD: jni::NativeMethod = native_method! {
    fn Calculator::multiply(a: jint, b: jint) -> jint,
};

impl Calculator<'_> {
    fn multiply<'local>(
        _env: &mut Env<'local>,
        _this: Calculator<'local>,
        a: jint,
        b: jint,
    ) -> Result<jint, jni::errors::Error> {
        Ok(a * b)
    }
}

§Exported Method with Type Mapping

const METHOD: jni::NativeMethod = native_method! {
    java_type = "com.example.MyClass",
    type_map = {
        unsafe MyHandle => long,
    },
    extern fn MyType::process(handle: MyHandle) -> JString,
};

impl MyType<'_> {
    fn process<'local>(
        env: &mut Env<'local>,
        _this: MyType<'local>,
        handle: MyHandle,
    ) -> Result<JString<'local>, jni::errors::Error> {
        JString::from_str(env, "processed")
    }
}

§Raw Method (No Wrapping)

const METHOD: jni::NativeMethod = native_method! {
    raw fn fast_compute(value: jint) -> jint,
};

fn fast_compute<'local>(
    _env: EnvUnowned<'local>,
    _this: JObject<'local>,
    value: jint,
) -> jint {
    value * 2
}

§Array of Methods for Registration

const METHODS: &[NativeMethod] = &[
    native_method! {
        fn add(a: jint, b: jint) -> jint,
    },
    native_method! {
        fn greet(name: JString) -> JString,
    },
    native_method! {
        static fn get_version() -> jint,
    },
    native_method! {
        raw fn fast_path(value: jint) -> jint,
    },
];

fn add<'local>(
    _env: &mut Env<'local>, _this: JObject<'local>, a: jint, b: jint
) -> Result<jint, jni::errors::Error> { Ok(a + b) }

fn greet<'local>(
    env: &mut Env<'local>, _this: JObject<'local>, name: JString<'local>
) -> Result<JString<'local>, jni::errors::Error> {
    JString::from_str(env, &format!("Hello, {}", name.try_to_string(env)?))
}

fn get_version<'local>(
    _env: &mut Env<'local>, _class: JClass<'local>
) -> Result<jint, jni::errors::Error> { Ok(1) }

fn fast_path<'local>(
    _env: EnvUnowned<'local>, _this: JObject<'local>, value: jint
) -> jint { value }

fn register_native_methods<'local>(
    env: &mut Env<'local>,
    class: JClass<'local>,
) -> Result<(), jni::errors::Error> {
    unsafe { env.register_native_methods(class, METHODS) }
}

§Type Safety

The macro ensures compile-time type safety by:

  • Generating an extern "system" wrapper that has the correct ABI for registration with the associated JNI signature
  • Type-checking arguments when calling your implementation function
  • Rejecting mismatches between the JNI signature and Rust types

Important: The macro cannot determine if a method is static or instance at compile time. You must specify static correctly to ensure the second parameter type (JClass vs JObject) matches. The abi_check property (enabled by default) adds runtime validation to catch registration errors.

§Wrapper Macros

You can create wrapper macros to inject common configuration:

macro_rules! my_native_method {
    ($($tt:tt)*) => {
        ::jni2::native_method! {
            jni = ::jni2,
            type_map = {
                // Common type mappings
            },
            $($tt)*
        }
    };
}

§See Also