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)

Download jar files using Maven

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).

Caching of Java libraries

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"

Using local repository Jar files in 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"

Java compilation

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++.

Step 0: write some java code

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.java

Step 1: compile the jar

We 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"

Step 2: use the jar

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!"

Options and configuration

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.xml

Manually 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:

options("java.parameters"=c("-Xprof","-Xrunhprof"))

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()

What else could you do?

  • Run ant tasks. - using the antrun plugin
  • Initialize production web servers and ‘APIs’. - using Jetty or Spring.
  • run any other maven plugin goal, for example:
# 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] ------------------------------------------------------------------------

Further work

  • Maven shade plugin
  • Simplify multiple dependencies
  • Support creation of pom.xml files from R
  • Tools to maintain maven settings.xml