Running Maven from Java: the Maven Embedder

This is probably the beginning of a series of articles about testing Maven plugins.

I’ll start with the Maven Embedder, which allows you to run an embedded Maven from a Java program. Note that we’re not simply running a locally installed Maven binary from a Java program; we run Maven taken from a Java library. So, we’re not forking any process.

Whether this is useful or not for your integration tests is your decision πŸ˜‰

The source code of the example used in this tutorial can be found here: https://github.com/LorenzoBettini/maven-embedder-example/

I like to use the Maven Embedder when using the Maven Verifier Component (described in another blog post). Since it’s not trivial to get the dependencies to run the Maven Embedder properly, I decided to write this tutorial, where I’ll show a basic Java class running the Maven Embedder and a few JUnit tests that use this Java class to build (with the embedded Maven) a test Maven project.

This is the website of the Maven Embedder and its description:

https://maven.apache.org/ref/3.9.6/maven-embedder/

Maven embeddable component, with CLI and logging support.

Remember: this post will NOT describe integration testing for Maven plugins; however, getting to know the Maven Embedder in a simpler context was helpful for me.

Let’s create a simple Java Maven project with the quickstart archetype

Let’s change the Java version in the POM to Java 17, use a more recent version of JUnit, and add another test dependency we’ll use later:

Let’s import the Maven Java project into Eclipse (assuming you have m2e installed in Eclipse).

Let’s add the dependencies for the Maven Embedder:

Getting all the needed dependencies right for the Maven Embedder is not trivial due to the dynamic nature of Maven components and dependency injection. The requirements are properly documented above.

Let’s replace the “App.java” inside “src/main/java/” with this Java class:

That’s just a simple example of using the Maven Embedder. We rely on its “doMain” method that takes the arguments to pass to the embedded Maven, the base directory from where we want to launch the embedded Maven, and the standard output/error where Maven will log all its information. In a more advanced scenario, we could store the logging in a file instead of the console by passing the proper “PrintStream” streams acting on files.

Let’s create the folder “src/test/resources” (it will be used by default as a source folder in Eclipse); this is where we’ll store the test Maven project to build with the Maven Embedder.

Inside that folder, let’s create another Maven project (remember, this will be used only for testing purposes: we’ll use the Maven Embedder to build that project from a JUnit test):

We rely on the fact that the contents of “src/test/resources” are automatically copied recursively into the “target/test-classes” folder. Eclipse and m2e will take care of such a copy; during the Maven build, there’s a dedicated phase (coming before the phase “test”) that performs the copy: “process-test-resources”.

Let’s replace the “AppTest.java” inside “src/test/java/” with this JUnit class:

The first test is simpler: it runs the embedded Maven with the goals “clean” and “verify” on the test project we created above. The second one is more oriented to a proper integration test since it also passes the standard system property to tell Maven to use another local repository (not the default one “~/.m2/repository”). In such a test, we use a temporary local repository inside the target folder and always wipe its contents before the test. This way, Maven will always start with an empty local repository and download everything from scratch for building the test project in this test. On the contrary, the first test, when running the embedded Maven, will use the same local repository of your user.

The first test will be faster but will add Maven artifacts to your local Maven repository. This might be bad if you run the “install” phase on the test project because the test project artifacts will be uselessly stored in your local Maven repository.

The second test will be slower since it will always download dependencies and plugins from scratch. However, it will be completely isolated, which is good for tests and makes it more reproducible.

Note that we are not running Maven on the test project stored in “src/test/reources” to avoid cluttering the test project with generated Maven artifacts: we build the test project copied in the “target/test-classes”.

In both cases, we expect success (as usual, a 0 return value means success).

In a more realistic integration test, we should also verify the presence of some generated artifacts, like the JAR and the executed tests. However, this is easier with the Maven Verifier Component, which I’ll describe in another post.

IMPORTANT: if you run these tests from Eclipse and they fail because the Embedded Maven cannot find the test project to build, run “Project -> Clean” so that Eclipse will force the copying of the test project from “src/test/resources” to “target/test-classes” directory, where the tests expect the test project. Such a copy should happen automatically, but sometimes Eclipse goes out of sync and removes the copied test resources.

If you run such tests, you’ll see the logging of the embedded Maven on the console while it builds the test project. For example, something like that (the log is actually full of additional information like the Java class of the current goal; I replaced such noise with “…” in the shown log below):

REMEMBER: this is not the output of the main project’s build; it is the embedded Maven running the build from our JUnit test on the test project.

Note that the two tests will build the same test project. In a more realistic integration test scenario, each test should build a different test project.

If you only run the second test after it finishes, you can inspect the “target/test-classes” to see the results of the build (note the “local-repo” containing all the downloaded dependencies and plugins for the test project and the generated artifacts, including test results, for the test project):

Now, you can continue experimenting with the Maven Embedder.

In the next articles, we’ll see how to use the Maven Embedder when running Maven integration tests (typically, for integration tests of Maven plugins), e.g., together with the Maven Verifier Component.

Stay tuned πŸ™‚

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.