# Migrating to 0.22
The 0.22 release significantly improves safety and ergonomics through changes to
thread attachment, environment handling, and the addition of compile-time macros
for JNI strings, signatures as well as full Java type bindings.
## Quick Migration Checklist
Essential changes that affect most code:
- [ ] **Environment types**: Replace `JNIEnv` with `EnvUnowned` in native method
signatures and use `Env` when calling JNI APIs
- [ ] Update native method implementations to acquire an `Env` reference via
`EnvUnowned::with_env[_no_catch]()` before calling JNI methods
- [ ] Update native methods to pick an `ErrorPolicy` for mapping
`EnvOutcome` results into return values (such as `ThrowRuntimeExAndDefault`)
- [ ] **Thread attachment**: Update to `JavaVM::attach_current_thread()` which
now takes a closure with an `Env` reference.
- [ ] Replace any `Executor` usage with `JavaVM` attachment APIs
- [ ] Replace any daemon thread attachments with permanent attachments via
`attach_current_thread()` (See the [No Daemon Thread Attachments](#no-daemon-thread-attachments)
section for more details)
- [ ] **Type renames**: `GlobalRef` -> `Global`, `AutoLocal` -> `Auto`, `JavaStr` -> `MUTF8Chars`
- [ ] **Add type parameters**: `Global<T>`, `Weak<T>`, `JObjectArray<T>` now generic over Java types
- [ ] **JNI strings**: Replace runtime string encoding with `jni_str!("...")` macro
- [ ] **Signatures**: Parse signatures at compile-time with `jni_sig!((args) -> ReturnType)` macro
- [ ] **Throw Errors**: Take into account that `env.throw*` APIs will return
`Err(Error::JavaException)` after throwing (which should typically be
propagated to callers via '?')
Optional but recommended:
- [ ] **JavaVM singleton**: Use `JavaVM::singleton()` to get a reference to the
single `JavaVM` instance instead of passing around `JavaVM` references
- [ ] Use `bind_java_type!` macro for new Java object bindings
- [ ] Use `Reference::lookup_class`, `Env::load_class` or `LoaderContext` APIs
for class loading instead of `Env::find_class` (especially on Android)
- [ ] Use `native_method!` macro for defining native methods instead of manually
defining them with `#[no_mangle]` and `extern "system"` (if not using
`bind_java_type!` which can also define native methods)
- [ ] Use `jni_mangle!` macro for manually defined native methods to handle
export name mangling (if not using `native_method!` or `bind_java_type!`)
- [ ] If using `Auto` for local reference management, use `my_local.auto()` to
convert any local reference into an `Auto<T>`.
This document will focus on providing some brief context and examples for the
biggest changes since 0.21, but please also refer to the
[CHANGELOG.md](../../CHANGELOG.md), PR history and documentation for the new
APIs for more details.
**Note:** where possible, when APIs have been renamed or replaced, then
some updated version of the old API may be left in place as a hidden, deprecated
API in order to provide sign posting for the new / replacement APIs via the
compiler.
# JavaVM::singleton()
Firstly; since JNI doesn't support multiple JavaVM instances (and also doesn't
support unloading one JVM and then loading another), jni 0.22 adds a
`JavaVM::singleton()` method that allows you to get a reference to the single
`JavaVM` instance.
The singleton is either initialized via `JavaVM::from_raw` or via the invocation
APIs (`JavaVM::new` or `JavaVM::with_libjvm`) and it's guaranteed to be
initialized by the time you see an `Env` reference.
Being able to rely on this helps ensure the object bindings in Rust can be
transparent wrappers around a JNI reference, without ever needing to explicitly
retain a reference to the `JavaVM`.
For example `Global` object bindings can rely on `JavaVM::singleton()` and
`JavaVM::attach_current_thread_for_scope` to be able to delete their global
reference on `Drop`. This is based on the fact that it's not possible to
construct a `Global` without a valid `Env` reference.
# Thread Attachment Changes
The new thread attachment APIs in 0.22 are designed to never give you an
environment wrapper that you own. Instead, you are only ever given a reference
within a closure that is guaranteed to remain valid, and borrowed from a fixed
position on the stack, until the end of the closure.
### Separation of FFI and API environment types
To separate concerns; there are now two separate environment types:
- `EnvUnowned` represents a raw, FFI-safe, transparent wrapper around the JNI
environment pointer that is only use for native method arguments.
These are "unowned" in the sense that the JVM has attached the current thread
on your behalf before calling your native method. You are given an attached
environment pointer by the JVM and you're not responsible for detaching it.
- `Env` represents a safe wrapper around the JNI environment that is only ever
given to you as a reference within a closure, and which you need in order to
call JNI methods.
This type is not a transparent, FFI-safe wrapper and cannot be used for native
method arguments.
This is where all the interesting JNI APIs live, that were previously
accessible via `JNIEnv`.
An `EnvUnowned` needs to be explicitly upgraded to an `Env` before you can
call any JNI methods.
**Note:** for safety and signposting purposes, the old `JNIEnv` type is an alias
for `EnvUnowned` with a deprecation warning that explains this API change.
### Owned Thread Attachments
In the common case where you need to use JNI from a thread that the JVM hasn't
attached for you (i.e. within a thread you have spawned, outside of a native
method) you can use `JavaVM::attach_current_thread` like this:
```rust
use jni::{JavaVM, errors::Result};
fn example() -> Result<()> {
let jvm = JavaVM::singleton()?; // Or see `JavaVM::new` if you need to ensure the JVM is initialized
jvm.attach_current_thread(|env| {
// Use `env` to call JNI methods here
Ok(())
})
// Note: technically the thread is still attached at this point (so it's
// cheap to call `attach_current_thread` again) and it will be automatically
// detached when the thread exits.
//
// It's not necessary, but if you want to you can also safely detach the thread
// here by calling `jvm.detach_current_thread()`.
}
```
This is similar to `JavaVM::attach_current_thread_permanent` in jni 0.21.
If you used `JavaVM::attach_current_thread` in jni 0.21 to create a
scoped attachment try to use `JavaVM::attach_current_thread` in jni 0.22
for a permanent attachment where possible because there's almost no
difference from an API perspective but you're more likely to avoid unnecessary
attach/detach overhead if you can create a permanent attachment.
If you strictly need a scoped attachment then you can use
`JavaVM::attach_current_thread_for_scope`.
**Note:** All the thread attachment APIs will have no effect on the JNI attachment
if the thread is already attached. So for example you can't upgrade a previous
scoped attachment into a permanent attachment and a scoped attachment can't
detach a thread that was previously attached permanently.
**Note:** Unlike in jni 0.21 the thread attachment APIs in jni 0.22 will also
push a new JNI stack frame before calling your closure. The avoids issues where
you might have a long running thread on which you repeatedly call
`attach_current_thread` in order to use JNI and inadvertently leak a large
number of local references to the base JNI stack frame.
**Note:** All of the `attach_current_thread*` APIs in jni 0.22 will catch
(clear) any pending Java exceptions and map them to `Error::CaughtJavaException`,
considering that there is no JVM for exceptions to propagate to at this point
(unlike for native methods).
**Note:** if you used the `Executor` API in jni 0.21 you should hopefully be
able to easily migrate to the `JavaVM` attachment APIs which are very similar.
If you were using `Executor::with_attached_capacity` then you can consider using
`JavaVM::attach_current_thread_with_config` but in most cases you can just rely
on the fact that the JavaVM attachment APIs will also push a new JNI stack frame
before calling your closure.
### Unowned (Native Method) Thread Attachments
If you're implementing a native method then you won't be responsible for
attaching the thread to the JVM and you will instead be passed an environment
pointer as the first argument that represents an implicit ("unowned") thread
attachment that the JVM has already created for you.
jni 0.22 adds an `EnvUnowned` type that is an FFI safe wrapper around a
`sys::JNIEnv` pointer that has been passed as the first argument to a native
method call, and represents an implicit ("unowned") JNI thread attachment.
For example, you can use it with a native method implementation like this:
```rust,no_run
use jni::objects::{JObject, JString};
use jni::errors::ThrowRuntimeExAndDefault;
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_example_MyClass_myNativeMethod<'caller>(
mut unowned_env: jni::EnvUnowned<'caller>,
_this: JObject<'caller>,
arg: JString<'caller>,
) -> JObject<'caller> {
unowned_env.with_env(|env| -> jni::errors::Result<_> {
// Use `env` to call Java methods or access fields.
Ok(JObject::null())
}).resolve::<ThrowRuntimeExAndDefault>()
}
```
**Note:** The `native_method` and `bind_java_type` macros can be used to hide
the details of `EnvUnowned` (as well as handle export name mangling and adding
type checks to ensure the native method implementation matches the Java
signature) (See the `native_method` and `bind_java_type` sections below for more
details).
# `Global` + `Weak` are generic over the Java types they reference
In jni 0.21 `GlobalRef` and `WeakRef` erased the type of the Java object it
referenced (so a global reference could only be safely dereferenced as a
`JObject`)
In jni 0.22, `Global` now has a type parameter like `Global<JClass>` and it can
be dereferenced to the specific Java type it references so you don't need to use
`unsafe` casts to get back to the specific type of Java object you are working
with.
Additionally, `Global` and `Weak` are now simpler, transparent wrappers around
the raw JNI reference pointers with no internal `Arc` or ref counting. The types
are `Send` + `Sync` and implement `Default` (as a null reference) so they can
be easily stored in your own state structs and dropped when no longer needed.
Note: `Global` and `Weak` no longer implement `Clone` (previously `Clone` would
bump the ref count for an inner `Arc`) but you can effectively clone them via
`Env::new_global_ref` or `Env::new_weak_ref`, like any other JNI reference.
# `JObjectArray` now generic over the type of its elements
In jni 0.21 `JObjectArray` erased the type of the Java objects in the array (so
you could only safely get elements as `JObject`)
In jni 0.22, `JObjectArray` now has a type parameter like `JObjectArray<JString>`
and you can get elements as the specific Java type they are so you don't need to
use `unsafe` casts to get back to the specific type of Java object you are
working with.
Multi-dimensional arrays can be composed like
`JObjectArray<JObjectArray<JString>>` or with primitive types like
`JObjectArray<JIntArray>`.
## Example: Creating and accessing a `JObjectArray<JString>`
```rust
use jni::{errors::Result, Env, objects::{JObjectArray, JString}};
fn example<'local>(env: &mut Env<'local>) -> Result<()> {
// Create some strings
let str1 = JString::from_str(env, "hello")?;
let str2 = JString::from_str(env, "world")?;
// Create an array of 2 strings, with all elements initially set to str1
let arr = JObjectArray::<JString>::new(env, 2, &str1)?;
// Set the second element to str2
arr.set_element(env, 1, &str2)?;
// Get elements from the array
let element0: JString = arr.get_element(env, 0)?;
let element1: JString = arr.get_element(env, 1)?;
assert_eq!(element0.to_string(), "hello");
assert_eq!(element1.to_string(), "world");
// Get the length of the array
let len = arr.len(env)?;
assert_eq!(len, 2);
Ok(())
}
```
# Native Method ErrorPolicies For Mapping Results
When you implement a native method in Rust you need to decide how to handle
errors (including panics) that may occur in your Rust code before you can return
a result to Java.
With jni 0.21 there was no built-in assistance for this.
With jni 0.22 and the introduction of `EnvUnowned` you are forced to explicitly
acquire a temporary `Env` within a closure that may return a `Result` and then
you are required to map an `EnvOutcome` into some value that can be returned to
Java.
An `EnvOutcome` is like a `Result` except you're shielded from unwrapping it
with a potential panic at an FFI boundary and you can encapsulate how you want
to map an outcome through the implementation of an `ErrorPolicy` (this mapping
process can itself use JNI).
Built-in policies allow you to automatically convert Rust errors into Java
exceptions or alternatively log an error and return a `Default` value.
For example a native method implemented like this with jni 0.21:
```rust,ignore
use jni::{JNIEnv, objects::{JObject, JString}};
#[no_mangle]
pub extern "system" fn Java_com_example_MyClass_myNativeMethod<'local>(
env: JNIEnv<'local>,
_this: JObject<'local>,
arg: JString<'local>,
) -> JObject<'local> {
// Your implementation went here, with no built-in assistance for
// Rust error handling or panic safety
JObject::null()
}
```
can be implemented like this with jni 0.22 to automatically convert Rust errors
and panics into Java exceptions:
```rust,no_run
use jni::{jni_mangle, EnvUnowned, objects::{JObject, JString}, errors::ThrowRuntimeExAndDefault};
#[jni_mangle("com.example.MyClass")]
pub fn my_native_method<'local>(
mut unowned_env: EnvUnowned<'local>,
_this: JObject<'local>,
arg: JString<'local>,
) -> JObject<'local> {
unowned_env.with_env(|env| -> jni::errors::Result<_> {
// Your implementation here
// - you can return an Err to throw a Java exception
// - a panic will also be caught and converted into a Java exception
// (use with_env_no_catch if you don't want to catch panics)
Ok(JObject::null())
}).resolve::<ThrowRuntimeExAndDefault>()
}
```
**Note:** the example uses `jni_mangle` for comparison with the jni 0.21 example
but for better safety and ergonomics it would be recommended to use
`native_method` or `bind_java_type`.
# Reference Trait and Binding Conventions
The `Reference` trait and associated conventions are designed to allow you to
define Rust types that are transparent wrappers around JNI references to Java
objects, and to ensure that these types can be used ergonomically and
efficiently, with an amortized cost for caching class references and
method/field IDs.
All the `jni-rs` reference types (e.g. `JObject`, `JClass`, `Global`, `Weak`,
`JObjectArray`) implement the `Reference` trait and follow these conventions.
The `bind_java_type` macro provides a concise/convenient way of implementing new
`Reference` types that follow all the associated conventions.
See the documentation for the `Reference` trait for more details, including the
conventions and how to implement it manually if you don't want to use the
`bind_java_type` macro.
# Class Loading and Loader Contexts
With jni 0.21, the go-to approach for loading classes was to use `Env::find_class`
with no explicit class loader, no support for caching and no ability to influence
the loading process (e.g. to allow loading of application classes on Android from
non-main threads).
With jni 0.22, all `Reference` types support loading (and caching) their class
reference via `Reference::lookup_class` and this lookup process can refer to a
`LoaderContext` that allows you to provide a specific class loader or reference
object (expected to be associated with a suitable class loader) to load the
class from.
Get the (cached) `JClass` for a `Reference` type (such as `JString`) like this:
```rust
use jni::{Env, objects::JString, refs::Reference as _, refs::LoaderContext};
fn example<'local>(env: &mut Env<'local>) -> jni::errors::Result<()> {
let class = JString::lookup_class(env, &LoaderContext::default())?;
Ok(())
}
```
Lookup a class by name like this:
```rust
use jni::{Env, refs::LoaderContext, jni_str};
fn example<'local>(env: &mut Env<'local>) -> jni::errors::Result<()> {
let class = env.load_class(jni_str!("com.example.MyClass"))?;
Ok(())
}
```
**Note:** `Env::load_class` and `LoaderContext::load_class` are based on
`java.lang.Class.forName` and use `.` dot separators for package names instead
of `/` slashes.
## Thread Context Class Loader
By default, all internal `LoaderContext` lookups will try and refer to the Java
Thread Context Class Loader, if no specific class loader is provided.
This pattern makes it possible for you to associate a thread with a default
class loader via `jni::objects::JThread::set_context_class_loader` so future
class lookups with a `LoaderContext` can refer to the thread context class
loader.
**Note:** this doesn't affect `Env::find_class` which continues to be a direct
binding to the JNI `FindClass` function that doesn't give you any control over
the class loading process. Ideally you can rely on `Reference::lookup_class`
or if you need to lookup a class by name then `Env::load_class` is
a shorthand for `LoaderContext::load_class` with a `None` context.
## Android Application Class Loading
JNI can be awkward to use on Android from non-main threads because `FindClass`
will only search for classes that are visible to the system class loader, and
so application code running on non-main threads can struggle to access
application classes (such as your Activity subclasses).
The traditional solution is to handle lookups from a `JNI_OnLoad` method so that
class lookups can be driven from the main thread and cached for other threads
but since this involves exporting a `JNI_OnLoad` function, this can be hard to
coordinate across orthogonal Rust library crates.
See here for more details:
<https://developer.android.com/ndk/guides/jni-tips#faq:-why-didnt-findclass-find-my-class>
With jni 0.21 one workaround was to manually:
1. get a reference to the `Activity` (such as via the `ndk_context` crate)
2. get the class loader from the `Activity`
3. perform a class lookup via that class loader
but this involved a fair amount of boilerplate for special-case lookups.
With jni 0.22 then Android frameworks that spawn non-main threads for
applications (such as the `android-activity` crate) can automatically set the
thread context class loader for those threads such that application class
lookups will just work by default when using the `LoaderContext` APIs (which
should be the case for all `Reference` types).
# Macros
jni 0.22 introduces several macros to simplify common patterns when working with
JNI and it's recommended to use these macros where possible.
## Compile-time encode JNI strings with `jni_str!` macro
Numerous JNI APIs require you to pass in strings encoded in the MUTF-8 format.
In jni 0.21 these APIs would _always_ incur a runtime cost for encoding and heap
allocating these strings every time they were called (even if you wanted to try
and pre-compute them you couldn't because the APIs required owned `JNIString`s).
In jni 0.22, all JNI methods that take MUTF-8 strings now expect an
`AsRef<JNIStr>` and since these APIs typically only need literal strings, this
allows you to use the `jni_str!` macro to create MUTF-8 encoded strings at
compile time.
### Example: Calling `Env::load_class`
```rust
use jni::{jni_str, errors::Result, Env, objects::JClass};
fn example<'local>(env: &mut Env<'local>) -> Result<()> {
let my_class = env.load_class(jni_str!("com.example.MyClass"))?;
Ok(())
}
```
**Note:** the `jni_str!` macro is similar to the `concat!` macro in that it can
produce literals that are composed from multiple literals.
## Compile-time parse JNI signatures with `jni_sig!` macro
With jni 0.21 then whenever you wanted to dynamically lookup and call a Java
method or get/set a field, you had to provide the method/field signature as a
Rust string, which would be parsed at runtime, validated and re-encoded as a
MUTF-8 string every time you called the method or accessed the field.
This was made worse by the fact that the output of parsing heap allocated a
representation that could include multiple String object names which weren't
needed.
In jni 0.22, all JNI methods that take method or field signatures now expect an
`AsRef<MethodSignature>` or `AsRef<FieldSignature>` and since these APIs
typically only need literal signatures, this allows you to use the `jni_sig!`
macro to parse JNI signatures at compile time.
### Example: Calling a Java method via `Env::call_method`
```rust
use jni::{jni_sig, jni_str, errors::Result, Env, objects::JObject, JValue};
fn example<'local>(env: &mut Env<'local>, obj: &JObject<'local>) -> Result<()> {
// Call a method with signature: String concat(String string)
let arg = env.new_string("world")?;
let result = env.call_method(
obj,
jni_str!("concat"),
// The signature is parsed at compile time, so there's no runtime overhead
jni_sig!((string: java.lang.String) -> java.lang.String),
&[JValue::Object(&arg)],
)?;
Ok(())
}
```
**Note:** the `jni_sig!` can parse raw JNI signatures like
`(Ljava/lang/String;)Ljava/lang/String;` but the example above shows how you can
use a more ergonomic syntax that looks more like a Rust function signature.
## Define individual native methods with `native_method!` macro
The `native_method!` macro can be used to define a single native method
implementation (as opposed to a full object binding via `bind_java_type`) and it
can handle the details of working with `EnvUnowned`, `catch_unwind`, error
handling, and export name mangling for you.
**SAFETY:** Using `native_method!` is recommended over manually defining a
native method with `#[no_mangle]` and `extern "system"` since it provides better
safety. The macro will automatically type-check that your implementation matches
the Java signature before the implementation is able to do anything with the
`Env` reference. In particular, it can catch common mistakes with the second
`this` or `class` arguments.
An equivalent implementation of the earlier `EnvUnowned` example using
`native_method` would look like this:
```rust,no_run
use jni::{native_method, Env, objects::{JObject, JString}};
const MY_NATIVE_METHOD: jni::NativeMethod = native_method! {
java_type = "com.example.MyClass",
extern fn my_native_method(arg: JString) -> JObject,
};
fn my_native_method<'local>(
env: &mut Env<'local>,
_this: JObject<'local>,
arg: JString<'local>,
) -> Result<JObject<'local>, jni::errors::Error> {
// Your implementation here - the macro handles EnvUnowned, catch_unwind,
// error handling, and export name mangling automatically.
// The `MY_NATIVE_METHOD` constant can optionally be used with Env::register_native_methods.
Ok(JObject::null())
}
```
## Manually mangle native method names with `jni_mangle!` macro
If for some reason you aren't able to use the `native_method!` macro to define
your native method, you can use the `jni_mangle!` macro to mangle the name of a
manually defined native method implementation.
For example, if you wanted to manually define the same native method as in the
previous example, you could do it like this:
```rust,no_run
use jni::{jni_mangle, Env, objects::{JObject, JString}};
#[jni_mangle("com.example.MyClass")]
pub extern "system" fn my_native_method<'caller>(
mut unowned_env: jni::EnvUnowned<'caller>,
_this: JObject<'caller>,
arg: JString<'caller>,
) -> JObject<'caller> {
unowned_env.with_env(|env| -> jni::errors::Result<_> {
// Your implementation here - the macro handles export name mangling automatically.
Ok(JObject::null())
}).resolve::<jni::errors::LogErrorAndDefault>()
}
```
**Note:** `jni_mangle` uses the `export_name` attribute internally so your
function is still accessible to your Rust code with the unmangled name (e.g.
`my_native_method` above).
**Note:** `jni_mangle` is similar to the [`jni_fn`][jni_fn] macro (it started as
a fork of `jni_fn`) but should have more-complete handling of the JNI name
mangling rules, including unicode characters and supporting overloaded methods.
It also adds support for mapping Rust snake case names to Java camelCase names
by default and uses `export_name` to avoid renaming the function itself.
[jni_fn]: https://crates.io/crates/jni_fn
## Define Java object bindings with `bind_java_type!` macro
With the introduction of the `Reference` trait and conventions, it's now
possible to define full Java object bindings in Rust that are transparent
wrappers around JNI references, with efficient caching of class references and
method/field IDs.
Since there's quite a bit of boilerplate involved in defining these bindings
manually, the `bind_java_type!` macro provides a concise and convenient way of
defining these bindings, with support for binding constructors, methods, fields,
and native methods, as well as customizing error handling and how panics are
mapped to Java exceptions.
The bindings generated by `bind_java_type` will implement the `Reference` trait
and adhere to the associated conventions.
Apart from a couple of low-level exceptions, all of the `jni-rs` reference types
are now internally implemented via `bind_java_type`.
### Example: Binding a Java class with a constructor, method and a field
```rust
use jni::{bind_java_type, Env};
use jni::objects::JString;
use jni::sys::jint;
// Define a binding for a Java class with constructors, methods, and fields
bind_java_type! {
pub Counter => com.example.Counter,
constructors {
/// Create a new Counter with initial value 0
fn new(),
/// Create a Counter with a specific initial value
fn with_value(initial: jint),
},
methods {
/// Increment the counter by 1
fn increment(),
/// Get the current counter value
fn get_value() -> jint,
},
fields {
/// The current counter value
value: jint,
/// The counter name
name: JString,
},
}
// Use the generated binding
fn example(env: &mut Env) -> jni::errors::Result<()> {
// Create a new Counter using the bound constructor
let counter = Counter::new(env)?;
// Call methods on the Counter
counter.increment(env)?;
let value = counter.get_value(env)?;
println!("Counter value: {}", value);
// Access and modify fields
counter.set_value(env, 42)?;
let name = JString::new(env, "MyCounter")?;
counter.set_name(env, &name)?;
Ok(())
}
```
### Example: Minimal type-only binding
In the simplest case where you don't even need a full object binding you can
create a one-line binding like this:
```rust
use jni::bind_java_type;
bind_java_type! { MyClass => "com.example.MyClass" };
```
_(For example you might do this just for the sake of having a
`MyClass::lookup_class` method that can cache the class reference for that
type)_
# Advanced Topics
## Lower-level `AttachGuard` API
Most crates should not consider using it but jni 0.22 does also provide
a lower-level unsafe `AttachGuard` API that can be used to wrap raw JNI
environment pointers.
An `AttachGuard` is an `unsafe` wrapper for an `Env` (that represents a thread
attachment) and it's what the various attachment APIs manage internally and then
borrow from to get a temporary `Env` reference.
Please refer very carefully to the safety docs for `AttachGuard` if you find
yourself needing to use it directly, but most crates should not need it.
## Safe access to the top JNI stack frame
In jni 0.21, `JNIEnv::unsafe_clone()` was sometimes used as an escape hatch to
get a new mutable `JNIEnv` that could be used to access the top JNI stack frame,
based on the restriction that no new local references escaped the scope in which
you created this clone. (This was cheaper than using `Env::with_local_frame`
because it didn't require pushing and popping a new JNI stack frame).
With jni 0.22, you can't clone an environment, but if you get into a situation
where you know the thread is attached and you want temporary (mutable) access to
the top JNI stack frame, then you can use `JavaVM::with_top_local_frame` or
`Env::with_top_local_frame` to get an `Env` reference within a given
closure. Unlike with `unsafe_clone()`, the Rust borrow checker ensures that any
local references you create can't escape the closure.
For example, if you have some API that only has access to a shared `Env`
reference (so you know the thread is attached) and you need to call a Java method
that returns a local reference, but you don't need to return that reference to the
caller, then you could use `Env::with_top_local_frame` like this:
```rust
use jni::{JavaVM, errors::Result, Env, objects::JObject, jni_str, jni_sig};
fn example<'any_local>(env: &Env, obj: impl AsRef<JObject<'any_local>>) -> Result<()> {
env.with_top_local_frame(|env| {
let result = env.call_method(obj, jni_str!("methodThatReturnsLocalRef"), jni_sig!("()Ljava/lang/Object;"), &[])?;
// Do something with `result` here, but it can't escape this closure
Ok(())
})
}
```
**Note:** beware that unlike `Env::with_local_frame`, then
`Env::with_top_local_frame` does not push and pop a new JNI stack frame and so
any local references you create will technically continue to exist after you
exit the closure and will only be released when the top JNI stack frame is
popped. This is safe and potentially cheaper than using `Env::with_local_frame`
but should probably not be used if you intend to create a large number of local
references with no clear bound on when the top JNI stack frame will be popped.
**Note:** In the unlikely situation where you could misuse the API to
materialize multiple mutable environment references that are visible from the
same Rust scope, there are runtime checks to ensure you can only ever create new
local references in the top JNI stack frame.
## No Daemon Thread Attachments
Daemon thread attachments are no longer supported in jni 0.22 since the
technical details that make them different from normal thread attachments also
imply a catch-22 that essentially makes them impossible to support safely in
Rust.
If you were previously using `JavaVM::attach_current_thread_as_daemon` in jni
0.21 you may not have been familiar with how daemon threads differ from normal
threads and you'll probably be fine just using `JavaVM::attach_current_thread`
in jni 0.22 for a permanent attachment instead.
The only time the semantics of a daemon thread attachment come into play is if
your application tries to destroy the JVM via `JavaVM::destroy()` or
`DestroyJavaVM` while a daemon thread is still attached, in which case the JVM
will not wait for those threads to detach or exit before destroying the JVM.
Destroying a JVM leaves it in a poorly defined state where it's undefined
behaviour to try and use JNI.
This creates a catch-22 because if you were using the `jni-rs` API to create a
daemon thread attachment, that implies a very high likelihood that you will also
inadvertently use the `jni-rs` API after the JVM is destroyed, due to the number
of RAII guard types in `jni-rs` that may automatically call JNI methods in their
`Drop` implementations.
### My application freezes after migrating away from daemon thread attachments
If you find that your application freezes or blocks when exiting, after migrating
away from daemon thread attachments this is a warning sign that you have an
unsound exit path where there may be threads running that have an invalid JNI
environment pointer and the potential to try calling JNI methods after
the JVM is destroyed.
The ideal solution (if you can't avoid calling `JavaVM::destroy()` /
`DestroyJavaVM`) is to identify those threads and ensure your application's exit
path can explicitly coordinate terminating those threads before destroying the
JVM.
For example if you have previously used daemon thread attachments to
integrate with `tokio`'s thread pool, then consider using
`tokio::runtime::Runtime::shutdown_now` to shutdown the runtime's thread pool
before destroying the JVM.
In the worst case, if you can't coordinate terminating all threads that are
attached to the JVM and you still need to destroy the JVM while leaving those
threads attached then you could resort to using
`(*jni::sys::JavaVM).v1_4.AttachCurrentThreadAsDaemon` manually and wrapping the
raw environment pointer via the `AttachGuard::from_unowned` or `EnvUnowned`
APIs. It would then also be your responsibility to call `jni::sys`
`DetachCurrentThread` and detach each thread before it exits (unless the JVM has
been destroyed). There's a big risk your application will be unsound when
exiting if you do this though, unless you have carefully audited how those
threads interact with the JVM and the `jni-rs` API.