---
title: "Using `rJavaEnv` in R Packages"
vignette: >
  %\VignetteIndexEntry{Using `rJavaEnv` in R Packages}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}
execute:
  eval: false
format:
  html:
    toc: true
    toc-depth: 2
    code-overflow: wrap
---

`rJavaEnv` helps package authors find, install and select Java. Choose your
integration according to whether Java runs as an executable, inside the R
process through `{rJava}`, or in a separate R process.

# Checks must preserve the ability to select Java

There are two different boundaries: loading the `{rJava}` namespace can load and
lock the JVM library path; `.jinit()` then starts the JVM. A namespace can be
loaded without `library(rJava)`, for example by `requireNamespace("rJava")`,
namespace imports, or IDE completion. Configure Java before any of these.

[Upstream rJava issue #334](https://github.com/s-u/rJava/issues/334) describes
path locking before initialization. Its experimental `feature/dynamic` branch
explores deferred loading; these examples do not assume that branch is installed.
[Issue #25](https://github.com/s-u/rJava/issues/25) explains why restarting a JVM
inside the same R process is not a reliable recovery strategy.

These checks do not load rJava or start its JVM in the calling R process:

- `java_check_version_cmd()` checks the executable under `JAVA_HOME` and returns
  its **major** version. It does not use an unrelated `java` earlier on `PATH`.
- `java_find_system()` discovers system installations.
- `java_resolve(21, install = FALSE)` finds a JDK without changing the calling
  session's environment. Its default `install = TRUE` permits downloading.
- `java_check_version_rjava(java_home = path)` tests rJava in a **fresh R process**.
  It returns the major version, or `FALSE` if rJava cannot be used there.
- `java_check_compatibility(21, action = "none")` reads an **already running**
  rJava JVM. It returns `FALSE` when no JVM is running; that means compatibility
  is unverified, not that it has checked and rejected a candidate installation.
  `action = "warn"`, `"message"` or `"stop"` reports an unverified or incompatible JVM.

`java_ensure()` and `use_java()` are setup operations. They can install Java and
change the session environment. `install = FALSE` prevents downloads; it does
not make `java_ensure()` read-only. Avoid setup, downloads and prompts in `.onLoad`.

# Command-line Java packages

A package that calls `system2()` or `processx::run()` can scope its environment.
Add `rJavaEnv` to `Imports` and use `local_java_env()` inside the wrapper:

```r
run_my_tool <- function(jar_path, input_file) {
  rJavaEnv::local_java_env(version = 21, type = "min")
  system2("java", c("-jar", shQuote(jar_path), shQuote(input_file)))
}
```

`JAVA_HOME` and `PATH` are restored when the function returns, including on error.
For a block, use `with_java_env()`:

```r
rJavaEnv::with_java_env(21, {
  system2("java", "-version")
}, install = FALSE)
```

These wrappers configure executables. Do not load rJava or an rJava-dependent
package inside the scope: its JVM can remain locked after the environment is
restored. A command-line wrapper can coexist with an already running rJava JVM;
changing its executable does not change that JVM.

Set `.use_cache = TRUE` for repeated checks when installations are stable.
By default suitable system Java is accepted. `accept_system_java = FALSE`
restricts resolution to managed installations matching the selected distribution,
backend, platform and architecture, or installs one when `install = TRUE`.

# Packages that defer loading rJava

If Java is an optional feature, list `rJava` in `Suggests` and `rJavaEnv` in
`Imports`. Do not import rJava symbols in `NAMESPACE`. This lets your package
load even before Java is configured, and makes an exported setup helper usable.

```r
setup_java <- function(version = 21, install = TRUE) {
  ready <- rJavaEnv::java_ensure(version, type = "min", install = install)
  if (!ready) stop("Java setup failed; configure Java before using this feature.")
  invisible(TRUE)
}

run_java_feature <- function(input) {
  # This must precede requireNamespace(), not just .jinit().
  setup_java()
  if (!requireNamespace("rJava", quietly = TRUE)) {
    stop("Install the rJava package to use this feature.")
  }
  rJava::.jinit()  # Deliberate initialization, after setup.
  rJavaEnv::java_check_compatibility(21, type = "min", action = "stop")
  # Your rJava operations go here.
}
```

If rJava is already loaded but its JVM is not running, `java_ensure()` errors
without starting it: the library path may already be locked and cannot safely
be inferred from `JAVA_HOME`. If its JVM is already running, a matching version
is accepted and an incompatible version causes an error without changing the
environment. Restart R before setup in either incompatible or unverified cases.

Experimental rJava builds with deferred JVM loading are an exception:
`java_ensure()` permits setup after namespace loading when their state API
explicitly reports that no JVM library has loaded and no JVM is initialized.
It also avoids preloading a JVM on Linux in that state. This supports a package
that imports rJava and defers Java operations until after setup. The exception
does not apply if a JVM library was already loaded, if another package has
started Java, or if the state cannot be verified. These experimental builds
are not required for the released-rJava patterns shown here.

`check_against = "cmd"` configures command-line Java even when rJava is loaded.
It does not establish compatibility for rJava or preload a JVM library.

# Packages that import rJava immediately

If your package has `Imports: rJava` and imports its symbols, or has
`Depends: rJava`, rJava may load before your own `.onLoad`. Your package's setup
helper cannot rescue a failed dependency load. Document setup through
`rJavaEnv` directly, independently of your package:

```r
# Start a fresh R session first if rJava was already loaded.
rJavaEnv::java_ensure(21, type = "min")
library(your_package)
```

Do **not** restart between setup and loading the package: `java_ensure()` sets
only the current session. To configure subsequent project sessions explicitly,
use `java_env_set(where = "project", java_home = path)` or `java_quick_install()`
with the user's consent. Choose project persistence explicitly; package startup
must not edit the user's profile automatically.

A compatibility guard can stop an operation after your package deliberately
initializes rJava. It does not repair a missing JVM, replace a setup step, or
change the rJava version:

```r
run_java_feature <- function(input) {
  rJava::.jinit()
  rJavaEnv::java_check_compatibility(21, type = "min", action = "stop")
  # Your rJava operations go here.
}
```

# Process isolation

For conflicting Java requirements, run rJava code in a fresh process:

```r
run_heavy_calc <- function(input_data) {
  rJavaEnv::with_rjava_env(
    version = 21,
    func = function(df) {
      rJava::.jinit()
      version <- rJava::.jcall("java.lang.System", "S", "getProperty", "java.version")
      list(version = version, rows = nrow(df))
    },
    args = list(df = input_data)
  )
}
```

Add `callr` to `Imports` if this is a required feature, or `Suggests` with a
clear availability check for an optional feature. rJava must be installed in
the library paths visible to the child. Return ordinary R data, not Java object
references or other pointers into the child process. The function must load its
own dependencies and receive its inputs through `args`.

A package that imports rJava can still use subprocess isolation once it has
loaded successfully, but it retains the initial dependency-load requirement.
An optional rJava dependency avoids that requirement in the parent.

# Version requirements

`use_java(21)` selects a managed Java 21 installation. `java_ensure(21,
type = "min")` can reuse a suitable system JDK of Java 21 or newer.
In `java_resolve()` and the functions built on it (`java_ensure()`,
`local_java_env()`, `with_java_env()` and `with_rjava_env()`), numeric patch
requests such as `"21.0.8"` compare full runtime versions rather than treating
every Java 21 runtime as equivalent. `use_java()` and `java_quick_install()` do
not check the installed runtime. Exact SDKMAN identifiers
select a managed installation by identity; `type = "min"` is unsupported for
identifiers. The identity of an already running rJava JVM cannot be established
from its runtime version alone, so identifier requests require a fresh setup.

# CI and end-to-end checks

Prefer setting Java at the workflow level, before installing or loading rJava:

```yaml
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '21'
```

A standalone `Rscript` step calling `java_ensure()` changes only that process;
later workflow steps will not inherit its environment. If using rJavaEnv for CI
setup, call it inside each R process before loading rJava, or explicitly export
the resolved paths through GitHub Actions' environment and path files.

The repository contains two minimal consumer packages under `tools/downstream`:
a lazy consumer and an eager rJava importer. Run their installed-package tests
from the repository root with two real JDKs:

```sh
RJE_JAVA_HOME_A=/path/to/jdk17 RJE_JAVA_HOME_B=/path/to/jdk21 \
  Rscript --vanilla tools/test-downstream.R
```

The runner installs this checkout and the consumers into a temporary library.
Each scenario uses a fresh R process. It verifies probe-before-switch behavior,
scoped cleanup on success and error, eager-import non-initialization, strict
handling of a locked JVM, and child-process isolation. It does not download JDKs
or modify the user's package library or cache. The dedicated GitHub Actions
workflow supplies Java 17 and 21 on Linux, macOS and Windows.
