Migrating to 0.21

Version 0.21 makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed accordingly.

This is a guide for migrating to 0.21. For a full list of changes in this release, please see the changelog.

Most of these changes are needed to ensure that all local references (JObject and the like) have the correct lifetime and can‘t be used after they’re deleted, which would cause undefined behavior. See issue #392 for a discussion of the problem these changes solve.

JNIEnv parameter of extern fns should now be mut

In extern fns that are directly called by the JVM, the JNIEnv parameter will usually need to be mut.

#[no_mangle]
pub extern "system" fn Java_HelloWorld_hello<'local>(mut env: JNIEnv<'local>,
                                                     class: JClass<'local>,
                                                     input: JString<'local>)
                                                     -> jstring {
    
}

This is needed because most JNIEnv methods now take a &mut self parameter.

JNIEnv, JObject, etc are now !Copy and should be borrowed

Functions that are not directly called by the JVM should, in most cases, borrow local references (JObject, JString, and so on) and mutably borrow JNIEnvs.

pub fn print_string(env: &mut JNIEnv,
                    string: &JString)
                    -> Result<()> {
    println!("{}", env.get_string(string)?.to_string_lossy());
    Ok(())
}

This is needed because these types no longer have the Copy or Clone traits.

JNIEnv::with_local_frame closure now takes a &mut JNIEnv parameter

When using JNIEnv::with_local_frame, Executor::with_attached, or Executor::with_attached_capacity, the closure must now take a parameter of type &mut JNIEnv.

env.with_local_frame(16, |env| {
    
})

The closure must only use the JNIEnv passed to it in that parameter, and not the JNIEnv that with_local_frame was called on.

JNIEnv::with_local_frame closure can return a generic Result

Note: This also applies to Executor::with_attached and Executor::with_attached_capacity which are thin wrappers over with_local_frame

The closure passed to with_local_frame is now free to return a generic Result as long as the error type implements From<jni::errors::Error>.

This can be particularly beneficial when running large amounts of code within a local frame in a crate that defines its own Result and Error types which need to be propagated to the caller.

There are a few trade offs with this change though:

  1. Sometimes the compiler won't be able to infer the generic error type (E.g. for code that simply returns Ok(())) and so it has to be explicitly specified
  2. Since it's no longer assumed that code always wants to return a single local reference this special case has to be handled differently

Two options for clarifying the error type for the compiler if it's ambiguous would be:

  1. Specify the type of the return value as part of an assignment:
let result: MyResult<()> = env.with_local_frame(10, |env| { Ok(()) });
  1. Specify the generic E error parameter:
env.with_local_frame::<_, _, MyError>(10, |env| { Ok(()) })?;

Code that returns a local reference to the calling frame can either use JNIEnv::with_local_frame_returning_local (which is marginally optimized for that special case) or else return a GlobalRef instead. (This approach works reasonably well in Rust, compared to C, because a global reference will be automatically deleted once it is dropped so it doesn't introduce a memory leak hazard like it would in C)

Passing object reference parameters to Java methods

When passing an object reference as a parameter to a Java method or constructor, it needs to be explicitly borrowed, as in (&obj).into(), instead of simply obj.into().

env.call_static_method(
    "com/example/SomeClass",
    "someStaticMethod",
    "(Ljava/lang/Object;)V",
    &[
        (&obj).into(),
    ],
)

JList and JMap methods all take a &mut JNIEnv parameter

All methods of JList and JMap now require a parameter of type &mut JNIEnv. They no longer store the JNIEnv that was used to construct them.

This is needed because most JNIEnv methods now take a &mut self parameter, and if the JList or JMap did store a &mut JNIEnv, then the caller would be unable to use the JNIEnv for anything else without first dropping the JList or JMap.

JList and JMap iterators no longer implement Iterator

Just like the methods of JList and JMap, their iterator now also requires a &mut JNIEnv in order to get the next element.

For this reason, the iterator returned by JList::iter and JMap::iter no longer implements std::iter::Iterator and can no longer be used with a for loop. Instead, it has a next method that takes a &mut JNIEnv parameter, and can be used with a while let loop.

let mut iterator = list.iter(env)?;

while let Some(obj) = iterator.next(env)? {
    let obj: AutoLocal<JObject> = env.auto_local(obj);
    // Do something with `obj` here.
}

Local references no longer leak

JNIEnv methods that look up Java classes no longer leak local references (#109), so it is no longer necessary to wrap calls to these methods inside JNIEnv::with_local_frame.

cannot borrow as mutable

If your project has a function that takes two or more parameters, one of them is JNIEnv, and another is something returned by a JNIEnv method (like JObject), then calls to that function may not compile.

fn example_function(
    env: &mut JNIEnv,
    obj: &JObject,
) {
    // …
}

example_function(
    env,
    // ERROR: cannot borrow `*env` as mutable more than once at a time
    &env.new_object(
        "com/example/SomeClass",
        "()V",
        &[],
    )?,
)

To fix this, the JNIEnv parameter needs to come last.

fn example_function(
    obj: &JObject,
    env: &mut JNIEnv,
) {
    // …
}

example_function(
    &env.new_object(
        "com/example/SomeClass",
        "()V",
        &[],
    )?,
    env,
);

invocation feature now finds and loads the JVM dynamically; JavaVM::new returns different error

The invocation feature has been changed to locate and load the Java Virtual Machine at run time, using the java-locator and libloading libraries.

JavaVM::new now returns a different error type, StartJvmError, when it fails. This new error type covers failures to locate and load the JVM library as well as failures to initialize the JVM.

If you need to load a specific JVM library instead of automatically discovering one, use JavaVM::with_libjvm instead.

On non-Windows platforms, it is no longer necessary for the Java runtime‘s lib folder to be on the shared library search path (LD_LIBRARY_PATH or equivalent). Unfortunately, this is not true on Windows, where the Java runtime’s DLLs must still be on the PATH (or added to the DLL search path with SetDllDirectory).

Desc has been redesigned

The Desc trait has been redesigned. If your project contains any implementations of Desc and/or calls to Desc::lookup, they will need to be updated.

If your project uses Desc directly like this, please post a comment on issue #403 explaining your situation. We view Desc as an implementation detail, and are considering sealing it and hiding its contents in a future release.

Changes to Desc in this release are as follows:

  • Desc is now an unsafe trait. The safety requirements haven't actually changed, but they were undocumented until now.

  • Desc now has an associated type, Output, which is now the type returned by the lookup method.

Please see the documentation for the Desc trait for more information.

JNIEnv::call_*method_unchecked is now unsafe

The JNIEnv::call_*method_unchecked methods are now unsafe. Their safety requirements haven't changed, but they were undocumented until now.

AutoLocal type and lifetime parameters changed

AutoLocal now has one lifetime parameter, representing the local reference frame it belongs to, and one type parameter, representing the type of object reference it contains (JObject, JClass, and so on).

This means that the object reference stored in an AutoLocal no longer has its type erased. If it was a JClass before wrapping it in AutoLocal, it will still be a JClass after.

// `AutoLocal` type parameters now include the type of object reference…
let auto_local: AutoLocal<'local, JClass<'local>>;

// …so the type of the object reference is now kept instead of being erased.
let local: &JClass<'local> = &*auto_local;

JObject no longer implements From<&AutoLocal> + From<&GlobalRef>

It is no longer possible to directly convert an &AutoLocal or &GlobalRef into a JObject. Instead, a JObject can be borrowed from AutoLocal or GlobalRef through their implementations of Deref and/or AsRef<JObject>.

let global_ref: GlobalRef;

// You can get a `JObject` from a `GlobalRef` two ways:
let object_ref: &JObject<'static> = &*global_ref;
let object_ref: &JObject<'static> = global_ref.as_ref();

JNIEnv::get_superclass now returns Option

The JNIEnv::get_superclass method previously returned a JClass, which would be null if the class in question doesn‘t have a superclass. It now returns Option<JClass> instead, and when it’s Some, the JClass inside is never null.

JNIEnv::{get,release}_string_utf_chars removed

The methods JNIEnv::get_string_utf_chars and JNIEnv::release_string_utf_chars have been removed. These methods have been replaced with JavaStr::into_raw and JavaStr::from_raw. To get a JavaStr, use JNIEnv::get_string or JNIEnv::get_string_unchecked. See issue #372 for discussion.

JPrimitiveArray<T> and JObjectArray provide safe reference wrappers (like JObject) for jarray

This affects all JNIEnv array APIs including:

  • new_<type>_array
  • get_array_length
  • get_object_array_element
  • set_object_array_element
  • get_<type>_array_region
  • set_<type>_array_region
  • get_array_elements
  • get_<type>_array_elements (all removed)
  • get_primitive_array_elements (also renamed to get_array_elements_critical)

With the exception of get_array_length these APIs now take or return a reference to a JPrimitiveArray or a JObjectArray instead of a sys type like jarray or jbyteArray. This improves safety since the sys types can be copied and easily read as invalid pointers after a reference has been deleted.

get_array_length will accept a reference to a JPrimitiveArray or a JObjectArray since these both implement the AsJArrayRaw trait. You can also query the .len() of an array via the AutoElements/AutoElementsCritical APIs if you use those.

There are the following type-specific aliases for JPrimitiveArray<T>:

  • JBooleanArray
  • JByteArray
  • JCharArray
  • JShortArray
  • JIntArray
  • JLongArray
  • JFloatArray
  • JDoubleArray

AutoArray and AutoPrimitiveArray renamed to AutoElements and AutoElementsCritical respectively

This rename was done to:

  1. Clarify the connection between these APIs (they both provide temporary access to the elements of an array)
  2. Clarify their differences (AutoElementsCritical is an alternative that defines a restricted “critical” section that helps JNI avoid the need to copy the data for the array)
  3. Differentiate these from the new JPrimitiveArray/JObjectArray types and aliases like JByteArray that represent the array itself (not the elements)

AutoElements<T> and AutoElementsCritical<T> now implement Deref<Target=[T]> and DerefMut

Previously accessing array elements required the use of unsafe code to access array elements via AutoArray::as_ptr() after acquiring an AutoArray guard.

It's now possible to read and write elements via the Deref and DerefMut traits that will deref into a [T] slice like:

let byte_array = env.new_byte_array(100)?;

{
    let mut elements = unsafe { env.get_array_elements(&byte_array, ReleaseMode::CopyBack) }?;

    elements[0] = 0xff;
    assert_eq!(elements[0], 0xff);

    // elements released (copied back to Java) here on Drop
}

If you are accessing a JPrimitiveArray<jbyte>/JByteArray you may find it helpful to utilize the bytemuck crate in case you need to access the elements as u8 unsigned bytes instead of i8.

Although this hasn't changed, it seems worth highlighting that if you are accessing a JPrimitiveArray<jboolean>/JBooleanArray you are responsible for only storing values of 0 or 1, since other values could lead to undefined behaviour for the JVM.

AutoArray/AutoPrimitiveArray::size() replace by AutoElements/AutoElementsCritical::len()

Previously AutoArray::size() was a wrapper for calling JNIEnv::get_array_length() which could fail, where as AutoElements and AutoElementsCritical now need to know the array length to be constructed, and this constant is accessible via the .len() method.

This change was required to support being able to Deref to a [T] slice.

get_array_elements[_critical] are unsafe

Previously get_array_elements and get_primitive_array_critical could be called by safe code but there were multiple ways in which these APIs could quite easily lead to undefined behaviour.

Since it is only possible to acquire an AutoElements and AutoElementsCritical instance via get_array_elements and get_primitive_array_critical, that's where we now document the safety rules that need to be followed to avoid any undefined behaviour.