The purpose of rmaven is to simplify the process of
including Java libraries into R code for use by rJava.
Currently the rJava infrastructure assumes jar files of
compiled code, and all the dependencies thereof, are available locally
and known to the programmer. This works well if the rJava
code is part of a package, but less well if the user is programming in a
REPL loop, and simply wants to use a Java library.
Even when packaging Java libraries with rJava code into
an R package, the transitive dependencies of a moderately complicated
bit of Java code can be complex, and distributing the jar
files associated with that burdensome, particularly when the underlying
libraries are updated. The bundling of jar files also may exceed the
stringent CRAN policies for allowable package sizes, requiring a complex
distribution mechanism for the dependencies.
To solve this rmaven manages some or all of this problem
for you, by integrating Java’s standard build and dependency management
tool Apache Maven into R. This allows Java libraries used within R to be
specified as Maven artifacts and transitive dependencies
downloaded dynamically from Maven repositories. rmaven is
pure R with minimal dependencies of its own, apart from the existence of
a Java runtime (JRE or JDK) installed on the
system, and the location known to R. It doesn’t even require
rJava (although without it there doesn’t seem much
point).
library(rmaven)
start_jvm()
# The following 3 lines clears the local rmaven cache and is here to
# create a clean slate before executing the rest of the vignette.
# In normal use you would not call this in a non-interactive setting
opts = options("rmaven.allow.cache.delete"=TRUE)
clear_rmaven_cache()
options(opts)When using rmaven the first step to using a Java library
in R is to fetch it from the Maven repositories. The artifact
coordinates (i.e. groupId, artifactId and
version) of an specific Java library are easily found on
the internet. Maven manages bootstrapping the download of libraries.
# cache apache commons lang3
dynamic_classpath = resolve_dependencies(
groupId = "org.apache.commons",
artifactId = "commons-lang3",
version="3.12.0",
verbose="quiet"
)
#> Updating Java dependencies, please be patient.
sprintf("Locally cached classpath of commons-lang3: %s",dynamic_classpath)
#> [1] "Locally cached classpath of commons-lang3: /home/runner/.cache/rmaven/.m2/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar"
# to get the one jar file many others are downloaded and cached as part of
# bootstrapping the Java build tools
sprintf("Jar files found in repository: %1.0f",
fs::dir_ls(get_repository_location(),recurse = TRUE,glob="*.jar") %>% length())
#> [1] "Jar files found in repository: 181"In Java development Maven manages a local repository in the
.m2/repository/ subdirectory of the users home. In
rmaven however changing directories in the user space
conflicts with CRAN policies. As the purpose of this library is to
support Java use in R, it makes sense to move the
.m2/repository/ cache directory to a CRAN approved
location, however this can be changed through configuration (see
set_repository_location(...) function).
As we saw above rmaven bootstraps loading of Maven
itself and any plugins required. When doing this the code above triggers
a significant download. Maven is good at handling the caching of
downloads, and sharing reused artifacts. Repeated invocation of
rmaven will not require additional downloads.
# does not need additional downloads
resolve_dependencies(
groupId = "org.apache.commons",
artifactId = "commons-lang3",
version="3.12.0"
)
#> [1] "/home/runner/.cache/rmaven/.m2/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar"
sprintf("Unchanged locally cached classpath of commons-lang3: %s",dynamic_classpath)
#> [1] "Unchanged locally cached classpath of commons-lang3: /home/runner/.cache/rmaven/.m2/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar"
# no additional downloads once cache populated
sprintf("Jar files found in repository: %1.0f",
fs::dir_ls(get_repository_location(),recurse = TRUE,glob="*.jar") %>% length())
#> [1] "Jar files found in repository: 181"rJava
The second step is to add jars from the local .m2
repository to the rJava class path, and using
rJava, create an R interface to the Java class you want to
use (in this case the StringUtils class).
rJava::.jaddClassPath(dynamic_classpath)
StringUtils = rJava::J("org.apache.commons.lang3.StringUtils")Finally you can call the static Java method, using
rJava, in this case
StringUtils.rotate(String s, int distance).
StringUtils$rotate("ABCDEF",3L)
#> [1] "DEFABC"Apart from using Maven to download manage dependencies, it also
allows us to compile Java code and integrate dependencies, specified in
a pom.xml file. By including Java code in an R project and
using Maven to compile code we raise the possibility of integrating Java
and R code in a manner similar to the way RCpp and
C++.
As part of your package write some Java code including a
pom.xml Maven build file, such as the
TestCass, in the example code bundled with this
package.
# find the java source code example bundled with this package
source_directory = system.file("testdata/test-project",package = "rmaven")
fs::dir_tree(source_directory, invert=TRUE, glob = "*/target/*")
#> /home/runner/work/_temp/Library/rmaven/testdata/test-project
#> ├── pom.xml
#> └── src
#> └── main
#> └── java
#> └── org
#> └── example
#> └── TestClass.javaWe use rmaven to compile the Java source code. This
could possibly be done in an .onLoad() package function, if
you were developing a package which includes Java source code:
# In this configuration rmaven will compile a single `fat-jar`
compiled_jar = compile_jar(source_directory, with_dependencies = TRUE)
#> Compiling Java library, please be patient.
#> [INFO] Scanning for projects...
#> [INFO]
#> [INFO] ------------------------------------------------------------------------
#> [INFO] Building test project 0.0.1-SNAPSHOT
#> [INFO] ------------------------------------------------------------------------
#> [INFO]
#> [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ test-project ---
#> [INFO] Using 'UTF-8' encoding to copy filtered resources.
#> [INFO] skip non existing resourceDirectory /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/src/main/resources
#> [INFO]
#> [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ test-project ---
#> [INFO] Changes detected - recompiling the module!
#> [INFO] Compiling 1 source file to /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/classes
#> [INFO]
#> [INFO] --- maven-assembly-plugin:3.2.0:single (default-cli) @ test-project ---
#> [INFO] Building jar: /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/test-project-0.0.1-SNAPSHOT-jar-with-dependencies.jar
#> [INFO] Building jar: /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/test-project-0.0.1-SNAPSHOT-src.jar
#> [INFO]
#> [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ test-project ---
#> [INFO] Using 'UTF-8' encoding to copy filtered resources.
#> [INFO] skip non existing resourceDirectory /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/src/main/resources
#> [INFO]
#> [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ test-project ---
#> [INFO] Nothing to compile - all classes are up to date
#> [INFO]
#> [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ test-project ---
#> [INFO] Not copying test resources
#> [INFO]
#> [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ test-project ---
#> [INFO] Not compiling test sources
#> [INFO]
#> [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ test-project ---
#> [INFO] Tests are skipped.
#> [INFO]
#> [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ test-project ---
#> [INFO] Building jar: /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/test-project-0.0.1-SNAPSHOT.jar
#> [INFO]
#> [INFO] --- maven-assembly-plugin:3.2.0:single (trigger-assembly) @ test-project ---
#> [INFO] Building jar: /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/test-project-0.0.1-SNAPSHOT-jar-with-dependencies.jar
#> [INFO] Building jar: /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/test-project-0.0.1-SNAPSHOT-src.jar
#> [INFO] ------------------------------------------------------------------------
#> [INFO] BUILD SUCCESS
#> [INFO] ------------------------------------------------------------------------
#> [INFO] Total time: 8.345 s
#> [INFO] Finished at: 2026-02-12T14:43:50+00:00
#> [INFO] Final Memory: 19M/74M
#> [INFO] ------------------------------------------------------------------------
sprintf("Location of compiled jar from test source code in this repository: %s",compiled_jar)
#> [1] "Location of compiled jar from test source code in this repository: /home/runner/.cache/rmaven/org.example_test-project_0.0.1-SNAPSHOT/test-project-0.0.1-SNAPSHOT/target/test-project-0.0.1-SNAPSHOT-jar-with-dependencies.jar"
# More plugins are required to perform the compilation
sprintf("Jar files found in repository: %1.0f",
fs::dir_ls(get_repository_location(),recurse = TRUE,glob="*.jar") %>% length())
#> [1] "Jar files found in repository: 269"Once compiled we can use the Java code within R in a very similar manner as before
rJava::.jaddClassPath(compiled_jar)
TestClass = rJava::J("org.example.TestClass")
TestClass$sayHelloWorld()
#> [1] "Hello world!"Various options exist to control behaviour of the library. N.B. None of the options in this section are executed as part of the vignette, they are just here to document the options.
Set the level of information returned by Maven. Maven can be very
verbose so defaulting rmaven to “quiet” mode is often a
good idea.
options("rmaven.quiet"=TRUE)Set the default repository URLs for fetching artifacts. these can
also be specified as options for the fetch_artifact() and
copy_artifact() functions. E.g. to restrict to Maven
central:
options("rmaven.default_repos" = c("https://repo1.maven.org/maven2/"))
# N.b. plan is to integrate this with settings.xmlManually set JAVA_HOME for Maven to use. The home
directory of the Java installation is detected automatically by firstly
checking this option, then the JAVA_HOME environment
variable, then the LD_LIBRARY_PATH environment variable,
then by asking rJava.
options("rmaven.java_home"="/usr/lib/jvm/java-9-oracle")Provide custom options to the JVM on startup, e.g. enabling
profiling, this needs to be set before start_jvm() to have
any effect:
Enable debug mode option has the dual effect of: starting the JVM in
debug mode (JVM flags:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8998,server=y,suspend=n
), and asking Maven to print debugging output (Maven flags:
-X -e). This needs to be set before
start_jvm() to have any effect.
options("rmaven.debug"=TRUE)Configure location of the local m2 repository, e.g. to use the Java default package location:
You might want to do this if you are a Java developer and you have a
maven cache already populated with locally built Java libraries from a
Java IDE and you are working to develop an R package pending deployment.
If you using rmaven for dependency management in a package
and you intend to submit it to CRAN, altering this setting for the
rmaven repository location will cause issues during CRAN
submission as the resulting package will touch the userspace and hence
violate CRAN’s policies.
# THIS IS NOT RUN (and nore are any of the other options documented
# in this section)
# options("rmaven.m2.repository"="~/.m2/repository/")Allow rmaven to programmatically delete all cached
files. This will involve a lot of downloading from maven central and
generally only required on a development machine where you want to make
sure everything runs from scratch (like in this vignette).
options("rmaven.allow.cache.delete"=TRUE)
# combined with clear_rmaven_cache()antrun plugin
# Here we execute a maven help goal
execute_maven("help:help")
#> [INFO] Scanning for projects...
#> [INFO]
#> [INFO] ------------------------------------------------------------------------
#> [INFO] Building Maven Stub Project (No POM) 1
#> [INFO] ------------------------------------------------------------------------
#> [INFO]
#> [INFO] --- maven-help-plugin:3.3.0:help (default-cli) @ standalone-pom ---
#> [INFO] Apache Maven Help Plugin 3.3.0
#> The Maven Help plugin provides goals aimed at helping to make sense out of the
#> build environment. It includes the ability to view the effective POM and
#> settings files, after inheritance and active profiles have been applied, as
#> well as a describe a particular plugin goal to give usage information.
#>
#> This plugin has 8 goals:
#>
#> help:active-profiles
#> Displays a list of the profiles which are currently active for this build.
#>
#> help:all-profiles
#> Displays a list of available profiles under the current project.
#> Note: it will list all profiles for a project. If a profile comes up with a
#> status inactive then there might be a need to set profile activation
#> switches/property.
#>
#> help:describe
#> Displays a list of the attributes for a Maven Plugin and/or goals (aka Mojo -
#> Maven plain Old Java Object).
#>
#> help:effective-pom
#> Displays the effective POM as an XML for this build, with the active profiles
#> factored in, or a specified artifact. If verbose, a comment is added to each
#> XML element describing the origin of the line.
#>
#> help:effective-settings
#> Displays the calculated settings as XML for this project, given any profile
#> enhancement and the inheritance of the global settings into the user-level
#> settings.
#>
#> help:evaluate
#> Evaluates Maven expressions given by the user in an interactive mode.
#>
#> help:help
#> Display help information on maven-help-plugin.
#> Call mvn help:help -Ddetail=true -Dgoal=<goal-name> to display parameter
#> details.
#>
#> help:system
#> Displays a list of the platform details like system properties and environment
#> variables.
#>
#>
#> [INFO] ------------------------------------------------------------------------
#> [INFO] BUILD SUCCESS
#> [INFO] ------------------------------------------------------------------------
#> [INFO] Total time: 4.902 s
#> [INFO] Finished at: 2026-02-12T14:43:56+00:00
#> [INFO] Final Memory: 16M/68M
#> [INFO] ------------------------------------------------------------------------