The Maven plugin for GraalVM Native Image adds support for building and testing native images using Apache Maven™.
Find the differences between the versions in the project changelog.
You can find sample applications in the source repository.
Adding the Plugin
Refer to the Getting Started Maven Guide which provides step-by-step directions on adding the Gradle plugin to your project, building your first native image, and running it.
The plugin requires that you install a GraalVM JDK. |
Plugin Configuration
Native Image Options
The plugin picks up all the configuration for your application stored below the META-INF/native-image/ resource location, as described in
Native Image Build Configuration.
It is also possible to customize the plugin within the <configuration>
block.
The following configuration options are available:
<mainClass>
-
If the execution fails with the
no main manifest attribute, in target/<name>.jar
error, the main class should be specified. By default the plugin consults several locations in pom.xml in the following order to determine what the main class should be:-
<maven-shade-plugin> <transformers> <transformer> <mainClass>
-
<maven-assembly-plugin> <archive> <manifest> <mainClass>
-
<maven-jar-plugin> <archive> <manifest> <mainClass>
-
<imageName>
-
Use
<imageName>
to set a custom filename for the native image. If a custom image name is not supplied, the artifact ID of the project will be used by default. <buildArgs>
-
If you want to pass additional build options to
native-image
, use the<buildArgs>
tag. You can pass any Native Image build option listed here.
<buildArgs>
<buildArg>--argument</buildArg>
</buildArgs>
<skipNativeBuild>
-
To skip generation of the native image, add:
<skipNativeBuild>true</skipNativeBuild>
<skipNativeTests>
-
To skip generation and execution of the native image compiled tests, add:
<skipNativeTests>true</skipNativeTests>
<debug>
-
If you want to enable generation of debugging information, add:
<debug>true</debug>
<verbose>
-
If you want to enable verbose output during the native image build, add:
<verbose>true</verbose>
<sharedLibrary>
-
If you want to build image as a shared library, add:
<sharedLibrary>true</sharedLibrary>
<useArgFile>
-
If you want to use the argument file for a native image building, add:
<useArgFile>true</useArgFile>
<quickBuild>
-
If you want to build a native image using quick build mode, add (alternatively, set the
GRAALVM_QUICK_BUILD
environment variable totrue
):
<quickBuild>true</quickBuild>
<excludeConfig>
-
To exclude configuration from present JAR files, specify:
<excludeConfig>
<entry>
<jarPath>dummy/path/to/file.jar</jarPath>
<resourcePattern>*</resourcePattern>
</entry>
</excludeConfig>
<environment>
-
To set environment options for a native image build, use:
<environment>
<variable>value</variable>
</environment>
<systemPropertyVariables>
-
To specify system properties used for a native image build, use:
<systemPropertyVariables>
<propertyName>value</propertyName>
</systemPropertyVariables>
<jvmArgs>
-
To specify JVM arguments used for a native image build, use:
<jvmArgs>
<arg>argument1</arg>
<arg>argument2</arg>
</jvmArgs>
<classpath>
-
Sets a custom classpath instead of the plugin generated one, use:
<classpath>
<param>path/to/file.jar</param>
<param>path/to/classes</param>
</classpath>
<classesDirectory>
-
If you want to specify custom path to the JAR, or a custom directory that contains only application classes, but you want the plugin to still automatically add classpath entries for dependencies, add:
<classesDirectory>
path/to/dir
</classesDirectory>
<agent>
-
Configuration of the native agent. See Enabling the Agent and Configuring the Agent for details.
For example, to build a native image named myapp
that uses org.example.ClassName
as its main class with assertions enabled, the <configuration>
should look like this:
<configuration>
<imageName>myapp</imageName>
<mainClass>org.example.ClassName</mainClass>
<fallback>false</fallback>
<verbose>true</verbose>
</configuration>
Most of the aforementioned properties can also be set on the command line as a part of Maven invocation. For example, if you want to temporarily enable verbose mode, you can append |
Native Image Tracing Agent
If your project requires reflection, classpath resources, dynamic proxies or other features requiring explicit native configuration, it may prove helpful to first run your application or tests using the Native Image Tracing Agent.
The Native Image Maven plugin simplifies generation of the required configuration files by injecting the agent automatically for you (this includes, but is not limited to the reflection configuration).
The agent generates the configuration file(s) under target/native/agent-output.
Although those files will be automatically used by native-image
, you should consider reviewing the generated files and adding them to your sources instead.
Enabling the Agent
The agent is disabled by default, but it can be enabled in the pom.xml file or on the command line.
To enable the agent by default, specify <enabled>true</enabled>
as follows in the configuration of native-maven-plugin
:
<configuration>
<agent>
<enabled>true</enabled>
</agent>
</configuration>
To enable the agent on the command line, pass the -Dagent=true
option when running Maven.
The examples in the following sections demonstrate how to do this for your application and for tests.
If you have enabled the agent in pom.xml, you can still disable it on the command line by passing |
Configuring the Agent
If you would like to configure the options for the agent — for example, to configure agent mode or advanced features such as Caller-based Filters and Access Filters - you can specify them in pom.xml as described bellow.
<configuration>
<agent>
<enabled>true</enabled>
<defaultMode>Standard</defaultMode>
<modes>
<direct>config-output-dir=${project.build.directory}/native/agent-output</direct>
<conditional>
<userCodeFilterPath>user-code-filter.json</userCodeFilterPath>
<extraFilterPath>extra-filter.json</extraFilterPath>
<parallel>true</parallel>
</conditional>
</modes>
<options>
<callerFilterFiles>
<filterFile>caller-filter-file1.json</filterFile>
<filterFile>caller-filter-file2.json</filterFile>
</callerFilterFiles>
<accessFilterFiles>
<filterFile>access-filter-file1.json</filterFile>
<filterFile>access-filter-file2.json</filterFile>
</accessFilterFiles>
<builtinCallerFilter>true</builtinCallerFilter>
<builtinHeuristicFilter>true</builtinHeuristicFilter>
<enableExperimentalPredefinedClasses>true</enableExperimentalPredefinedClasses>
<enableExperimentalUnsafeAllocationTracing>
true
</enableExperimentalUnsafeAllocationTracing>
<trackReflectionMetadata>true</trackReflectionMetadata>
</options>
<metadataCopy>
<!-- you can specify metadataCopy configuration here -->
</metadataCopy>
</agent>
</configuration>
This example shows all possibilities you can use for the agent configuration. If you do not need some of the options, just remove them.
Agent can be run in one of the following modes:
-
standard
- in this mode you run agent only with options provided in theoptions
section. -
direct
- in this mode you can provide command line options for the agent. In this mode, a user is fully responsible for agent configuration, and the rest of the agent configuration, provided in pom.xml file, will be ignored. -
conditional
- in this mode you can provide additional files that can be used as a filter for the agent output. Read more about conditional mode here.
Here is an explanation for each option you can specify in the agent block:
-
enabled
- Specifies whether the agent is enabled or not. Can be set totrue
orfalse
. -
defaultMode
- Agent mode switcher. Can be set to:standard
,direct
, orconditional
. -
modes
- A list of additional mode options, specific for certain mode type. Inside this tag, you can specify options for direct or conditional modes. Standard mode does not have any specific options.-
in case of
direct
mode, you can specify<direct>
tag, with the agent command line as its value -
in case of
conditional
mode, you can specify<conditional>
tag, and set additional filter files inside<userCodeFilterPath>
and<extraCodeFilterPath>
. Also, you can set value for<parallel>
tag. If the value istrue
, the agent will create the partial-config file, and merge it with conditional merge. Otherwise the agent will generate the same kind of output as other modes.
-
-
options
- A list of options that can be specified independently from the agent mode. Learn more about these options here.
Running Tests with the Agent
The simplest way to use the agent is to do it via execution of your tests.
Run your test suite with:
./mvnw -Pnative -Dagent=true test
When the agent
system property is set to true
(or when the agent is enabled in the POM), the agent will be automatically attached to your Maven Surefire test execution, and the generated files will be stored in the target/native/agent-output/test directory.
If you want to run the metadataCopy task as well, first define its configuration as described above, and add
|
Running the Application with the Agent
Executing your application with the agent is more involved and requires you to configure a separate Mojo execution which allows forking the Java process.
In your native
profile section, add the following:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>java-agent</id>
<goals>
<goal>exec</goal>
</goals>
<phase>test</phase>
<configuration>
<executable>java</executable>
<workingDirectory>${project.build.directory}</workingDirectory>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>${mainClass}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>java-agent</id>
<goals>
<goal>exec</goal>
</goals>
<phase>test</phase>
<configuration>
<executable>java</executable>
<workingDirectory>${project.build.directory}</workingDirectory>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>${mainClass}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
Then you can execute your application with the agent by running:
./mvnw -Pnative -Dagent=true -DskipTests -DskipNativeBuild=true package exec:exec@java-agent
Both of the above commands generate configuration files in the target/native/agent-output/main directory. If you want to run your native application with those configuration files, you then need to execute the following command:
./mvnw -Pnative -Dagent=true -DskipTests package exec:exec@native
Reducing the Amount of Generated Metadata
In some cases the agent may include more metadata than it is actually needed. You can filter metadata using the agent filter file(s). These filter files that agent consumes have the following structure:
{
"rules": [
{"includeClasses": "some.class.to.include.**"},
{"excludeClasses": "some.class.to.exclude.**"},
],
"regexRules": [
{"includeClasses": "regex\.example\.class.*"},
{"excludeClasses": "regex\.example\.exclude[0-9]+"},
]
}
The process how you can pass the configuration files to the agent is described in the previous section.
You can see on some simple Java application how different filter files affect generated metadata.
Let’s start with this filter:
{
"rules": [
{"includeClasses": "**"}
]
}
This filter instructs the agent to include everything, which will result in a massive configuration file. For example, this is how reachability-metadata.json looks like:
{
"reflection": [
{
"condition": {
"typeReached": "java.util.concurrent.atomic.AtomicBoolean"
},
"type": "java.util.concurrent.atomic.AtomicBoolean",
"fields": [
{
"name": "value"
}
]
},
{
"condition": {
"typeReached": "org.apache.maven.surefire.booter.ForkedBooter"
},
"type": "org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory"
},
{
"condition": {
"typeReached": "org.apache.maven.surefire.booter.ForkedBooter"
},
"type": "org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory"
},
{
"condition": {
"typeReached": "org.junit.platform.launcher.core.DefaultLauncher"
},
"type": "org.apiguardian.api.API"
},
{
"condition": {
"typeReached": "java.lang.Class"
},
"type": "org.example.NativeTests"
},
{
"condition": {
"typeReached": "java.util.Collections$2"
},
"type": "org.example.NativeTests"
},
{
"condition": {
"typeReached": "org.apache.maven.surefire.api.util.DefaultScanResult"
},
"type": "org.example.NativeTests"
},
{
"condition": {
"typeReached": "org.junit.jupiter.engine.JupiterTestEngine"
},
"type": "org.example.NativeTests"
},
{
"condition": {
"typeReached": "org.junit.jupiter.engine.discovery.ClassSelectorResolver$$Lambda/0x00007a5c0f03fbb8"
},
"type": "org.example.NativeTests"
},
{
"condition": {
"typeReached": "java.security.Provider$Service"
},
"type": "sun.security.provider.SHA",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
}
...
],
"resources": [
{
"condition": {
"typeReached": "jdk.internal.logger.BootstrapLogger$DetectBackend$1"
},
"glob": "META-INF/services/java.lang.System$LoggerFinder"
},
{
"condition": {
"typeReached": "jdk.internal.logger.LoggerFinderLoader"
},
"glob": "META-INF/services/java.lang.System$LoggerFinder"
},
{
"condition": {
"typeReached": "org.apache.maven.surefire.booter.ForkedBooter"
},
"glob": "META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory"
},
{
"condition": {
"typeReached": "java.lang.ClassLoader"
},
"glob": "TestResource.txt"
},
...
],
"bundles": [],
"jni": [
{
"condition": {
"typeReached": "sun.nio.ch.IOUtil"
},
"type": "java.lang.Boolean",
"methods": [
{
"name": "getBoolean",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"condition": {
"typeReached": "sun.management.VMManagementImpl"
},
"type": "sun.management.VMManagementImpl",
"fields": [
{
"name": "compTimeMonitoringSupport"
},
{
"name": "currentThreadCpuTimeSupport"
},
{
"name": "objectMonitorUsageSupport"
},
{
"name": "otherThreadCpuTimeSupport"
},
{
"name": "remoteDiagnosticCommandsSupport"
},
{
"name": "synchronizerUsageSupport"
},
{
"name": "threadAllocatedMemorySupport"
},
{
"name": "threadContentionMonitoringSupport"
}
]
}
]
}
As you can see, there are lots of entries that you likely do not want. They are present because the metadata was generated using tests, and that the testing library was present at run time during the agent execution pass, but in practice, your application will not use the testing library in production. To reduce the amount of generated metadata, use the following user-code-filter.json:
{
"rules": [
{"includeClasses": "**"},
{"excludeClasses": "org.apache.maven.**"},
{"excludeClasses": "org.junit.**"},
{"excludeClasses": "java.**"},
{"excludeClasses": "jdk.internal.**"},
{"excludeClasses": "sun.**"},
{"excludeClasses": "com.sun.**"}
]
}
Always be careful when removing entries in metadata, as this may result in a broken native image. |
After updating the filter, you can regenerate the metadata, which will result in the following reachability-metadata.json file:
{
"reflection": [
{
"condition": {
"typeReached": "org.example.NativeTests"
},
"type": "org.example.NativeTests$Person",
"allDeclaredFields": true
}
],
"resources": [
{
"condition": {
"typeReached": "org.example.NativeTests"
},
"glob": "TestResource.txt"
}
],
"bundles": []
}
As you can see, there are no more entries that contain classes from org.apache.maven
(as their condition).
Metadata Copy
The metadataCopy
task provides additional options for manipulating the agent output after it finishes its job.
<metadataCopy>
<disabledStages>
<stage>main</stage>
</disabledStages>
<merge>true</merge>
<outputDirectory>/tmp/test-output-dir</outputDirectory>
</metadataCopy>
You can set values for the following tags:
-
<outputDirectory>
- where you want to copy agent output. -
<merge>
- in case you already have some other configuration files inside output directory, you can choose whether you want to override those files or merge new files with the existing once (setmerge
totrue
). -
<disabledStages>
- in case you do not want to copy output of the certain stage (main or test), you can disable them, andmetadataCopy
will not look at the agent output for that stage. For example, if you want to copy only configuration files generated in tests, you can disable main stage. Also, if you want to copy only files generated in the main phase, you can disable the test stage. Therefore, if you skip both stages,metadataCopy
will not be executed.
Reusing Configuration from a Parent POM
The <buildArgs>
element can be combined between parent and children POMs.
Suppose you have the following parent POM definition:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${current_plugin_version}</version>
<configuration>
<imageName>${project.artifactId}</imageName>
<mainClass>${exec.mainClass}</mainClass>
<buildArgs>
<buildArg>--no-fallback</buildArg>
</buildArgs>
</configuration>
</plugin>
Children projects have the ability to append <buildArg>
arguments in the following way:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs combine.children="append">
<buildArg>--verbose</buildArg>
</buildArgs>
</configuration>
</plugin>
In this case, the arguments that are passed to the native-image
executable will be:
--no-fallback --verbose
Testing Support
This plugin supports running tests on the JUnit Platform as native images. This means that tests will be compiled and executed as native code.
The minimum supported version is JUnit 5.8.1 (JUnit Platform 1.8.1, JUnit Jupiter 5.8.1, JUnit Vintage 5.8.1). |
In theory, any TestEngine
supported on the JUnit Platform should be supported by the plugin as long as the programming language used by the TestEngine
and the programming language used to write the tests is supported by GraalVM Native Image.
The plugin provides explicit support for the JUnit Jupiter and JUnit Vintage test engines, and support for additional test engines should be possible with custom native configuration.
To use the recommended JUnit Platform test listener mode, you need to enable extensions for native-maven-plugin
by adding <extensions>true</extensions>
as follows.
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<extensions>true</extensions>
Running the ./mvnw -Pnative test
will then build and run tests as native code.
Version Compatibility
This plugin requires the Maven Surefire plugin 3.0.0 or higher to run tests as native code.
Each version of the Maven Surefire plugin has a dependency on a particular version of the JUnit Platform.
You therefore need to ensure that Maven Surefire is using at least version 1.8 of the JUnit Platform.
Beginning with version 3.0 M4, Maven Surefire automatically aligns the JUnit Platform version used by Surefire with the version needed by the user’s configured version of JUnit Jupiter or JUnit Vintage.
Since the examples here use Maven Surefire 3.0 M5, their maven POMs do not require any special configuration regarding the JUnit Platform version.
However, if you are using a version of Maven Surefire prior to 3.0 M4, you will need to add an explicit dependency on the junit-platform-launcher
artifact to the dependencies
section of your native
profile configuration. For example:
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
If you import the |
If Maven Surefire is using an older version of the JUnit Platform, the build will fail with an error similar to the following when attempting to run tests in a native image:
[ERROR] Test configuration file wasn't found. Make sure that test execution wasn't skipped.
Disabling Testing Support
If you wish to disable running tests on the JVM as well as tests as native code, you can invoke Maven with the -DskipTests
flag.
This flag is supported by Maven Surefire and Native Build Tools.
Several examples in Running the Application with the Agent demonstrate the use of this flag.
If you wish to run tests on the JVM with Maven Surefire but skip running tests as native code, you can invoke Maven with the -DskipNativeTests
flag.
This flag is specific to Native Build Tools.
For example, you might wish to disable only native testing support for use cases such as the following:
-
You do not actually want to run your tests as native code.
-
Your library or application uses a testing framework that is not supported on the JUnit Platform.
-
You need to use the agent when running tests on the JVM but do not wish to run those same tests as native code.
GraalVM Reachability Metadata Support
The plugin adds support for the GraalVM Reachability Metadata Repository. This repository provides the configuration for libraries that do not support GraalVM Native Image.
The repository is also published on Maven Central at the following coordinates:
For example: |
Configuring the Metadata Repository
The plugin automatically downloads the configuration metadata from the official repository if you supply the version of the repository you want to use.
The support is enabled by default, but can be disabled explicitly:
<metadataRepository>
<enabled>false</enabled>
</metadataRepository>
Alternatively, you can use a remote repository, in which case you can specify the URL of the ZIP file:
<metadataRepository>
<enabled>true</enabled>
<url>${metadata.url}</url>
</metadataRepository>
For debugging purposes you can use a local repository:
<metadataRepository>
<enabled>true</enabled>
<localPath>${project.basedir}/config-directory</localPath> (1)
</metadataRepository>
For each library included in the native image, the plugin will automatically search for GraalVM image build configuration metadata in the repository that was released together with the plugin.
For each library dependency, the plugin automatically searches for the configuration metadata in the repository. In some cases, you may want to override the repository version:
<metadataRepository>
<enabled>true</enabled>
<version>0.2.3</version>
</metadataRepository>
You may want to exclude a particular module from the search. This can be done by configuring that particular dependency:
<metadataRepository>
<enabled>true</enabled>
<localPath>${project.basedir}/config-directory</localPath>
<dependencies>
<dependency>
<groupId>org.graalvm.internal</groupId>
<artifactId>library-with-reflection</artifactId>
<excluded>true</excluded>
</dependency>
</dependencies>
</metadataRepository>
Lastly, it is possible for you to override the metadata version of a particular module. This may be interesting if there is no specific metadata available for the particular version of the library that you use, but that you know that a version works:
<metadataRepository>
<enabled>true</enabled>
<localPath>${project.basedir}/config-directory</localPath>
<dependencies>
<dependency>
<groupId>org.graalvm.internal</groupId>
<artifactId>library-with-reflection</artifactId>
<metadataVersion>2</metadataVersion>
</dependency>
</dependencies>
</metadataRepository>
Adding Metadata Repository Files
By default, reachability metadata is used only when your native image is being generated. In some situations, you may want to include the metadata directly inside your JAR file.
Copying the reachability metadata into the application JAR can be useful when some other process is responsible for converting your JAR into a native image. For example, you might be generating a shaded JAR file and using a Paketo buildpack to convert it to a native image.
To include metadata repository inside the JAR file, you can use the add-reachability-metadata
goal.
Typically, the goal will be included in an execution step where, by default, it will be bound to the generate-resources
phase:
<executions>
<execution>
<id>add-reachability-metadata</id>
<goals>
<goal>add-reachability-metadata</goal>
</goals>
</execution>
</executions>
Profile-Guided Optimization
The plugin supports building optimized images with Profile-Guided Optimization (PGO) to improve performance and throughput.
PGO is available in Oracle GraalVM. |
The PGO workflow includes three steps:
-
First, generate a native image with instrumentation enabled.
-
Next, run the image to gather profiling information.
-
Then, create an optimized native image based on the profiles.
To generate a binary with instrumentation enabled, pass the --pgo-instrument
option to native-image
using <buildArg>
, and run the build command ./mvnw -Pnative package
.
To prevent overwriting a previously built native executable, we recommend either creating a separate Maven profile for each build or specifying a unique file name using the <imageName>
tag:
<configuration>
<imageName>instrumentedApp</imageName>
<buildArgs>
<buildArg>--pgo-instrument</buildArg>
</buildArgs>
</configuration>
Next, gather execution profiles by running the instrumented executable:
./target/instrumentedApp
By default, the default.iprof file, if not specified otherwise, is generated alongside the native executable once the application is stopped.
Lastly, build an optimized native image with profiles by passing the --pgo
option in the plugin configuration:
<configuration>
<imageName>optimizedApp</imageName>
<buildArgs>
<buildArg>--pgo</buildArg>
</buildArgs>
</configuration>
If the profile file has the default name and location, it will be automatically used.
Alternatively, you can specify the file path as following: --pgo=myprofile.iprof
.
If everything was done properly, you will see "PGO: user-provided" in the native image build output.
It is possible to include more than one profile, in which case you should rename the .iprof files. Learn more about PGO on the website.
Long classpath, @argument File and Shading Support
Under Windows, it is possible that the length of the classpath exceeds what the operating system supports when invoking the CLI to build a native image.
The plugin automatically passes arguments to the native-image
tool from the argument file, instead of passing them directly.
There is also the native:write-args-file
goal that can be used to generate this argument file.
This can be useful in situations where native-maven-plugin
is not available, for example, when running native-image
in a Docker container.
The path to the arguments file is stored in the project properties under the key graalvm.native-image.args-file
, so that other Maven plugins further in the lifecycle can use it.
In case you are using a GraalVM version older than 21.3, you will however have to use a workaround, since the argument file wasn’t supported.
One option is to use a shaded JAR and use it instead of individual JAR files on classpath. For that, you need to setup the Maven Shade plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
</configuration>
</execution>
</executions>
</plugin>
If you need the testing support, add the JUnit Platform dependency explicitly:
<dependencies>
<dependency>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>junit-platform-native</artifactId>
<version>${junit.platform.native.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Then native-maven-plugin
needs to be configured to use this JAR instead of the full classpath:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<skip>false</skip>
<useArgFile>false</useArgFile>
<imageName>${imageName}</imageName>
<fallback>false</fallback>
<classpath>
<param>${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar</param>
</classpath>
</configuration>
</plugin>
Depending on the other plugins your build uses (typically the Spring Boot plugin), you might have to configure, in addition, the main class:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.buildtools.version}</version>
<configuration>
<imageName>${project.artifactId}</imageName>
<mainClass>${exec.mainClass}</mainClass>
<buildArgs>
<buildArg>--no-fallback</buildArg>
</buildArgs>
<classpath>
<param>
${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar
</param>
</classpath>
...
To be able to execute tests as native code, you also need:
-
Create a src/assembly/test-jar-with-dependencies.xml file with the following contents:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>tests</id>
<formats>
<format>jar</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.build.directory}/test-classes</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>test</scope>
</dependencySet>
</dependencySets>
</assembly>
-
Add the Maven Assembly plugin to your
native
profile:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<descriptors>
<descriptor>src/assembly/test-jar-with-dependencies.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-test-jar</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
-
Due to a limitation in Maven, you need to move the tests execution to the
integration-test
phase:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>integration-test</phase>
<configuration>
<classpath>
<param>${project.build.directory}/${project.artifactId}-${project.version}-tests.jar</param>
</classpath>
</configuration>
</execution>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
<configuration>
<classpath>
<param>${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar</param>
</classpath>
</configuration>
</execution>
</executions>
<configuration>
<skip>false</skip>
<useArgFile>false</useArgFile>
<imageName>${imageName}</imageName>
<fallback>false</fallback>
</configuration>
</plugin>
Finally, you need to execute tests using the integration-test
phase instead of test
:
./mvnw -Pnative integration-test
Refer to the Maven Shade plugin documentation for more details on how to configure shading, and the Maven Assembly plugin documentation to tweak what to include in tests.
Javadocs
For further reference, you can review the Javadocs of the plugin.