$ mvn clean mn:run
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< java.maven.junit:java-maven-junit >------------------
[INFO] Building java-maven-junit 0.1
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ java-maven-junit ---
[INFO] Deleting /Users/alvarosanchez/Micronaut/micronaut-maven-plugin/examples/java/target
[INFO]
[INFO] --- mn:4.0.0:run (default-cli) @ java-maven-junit ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /Users/alvarosanchez/Micronaut/micronaut-maven-plugin/examples/java/target/classes
13:36:32.665 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 919ms. Server Running: http://localhost:8080
Running an application in development mode
It watches for changes in the project tree, and if they match certain conditions, it will recompile and restart the application.
The plugin can handle changes in Java, Groovy and resource source directories by default.
Note that this plugin can work with any Micronaut version (and potentially with any Java application), since it only deals with file changes and JVM restarts.
Usage
You can use mn:run to run your application in development mode:
Including and excluding options
By default, this plugin watches for changes in the following locations:
-
/pom.xml. Only dependency changes may trigger an application restart. -
src/main/resources/*/. -
src/main/java/*/(if it exists). -
src/main/groovy/*/(if it exists).
You can further include or exclude some other locations:
<project>
<!-- ... -->
<build>
<plugins>
<!-- ... -->
<plugin>
<groupId>io.micronaut.maven</groupId>
<artifactId>micronaut-maven-plugin</artifactId>
<version>5.1.0-SNAPSHOT</version>
<configuration>
<watches>
<watch>
<directory>src/main/resources</directory>
<excludes>
<exclude>application-test.yml</exclude>
</excludes>
</watch>
<watch>
<directory>src/main/webapp</directory>
</watch>
</watches>
</configuration>
</plugin>
</plugins>
</build>
</project>
Note that exclusions take precedence over inclusions, so if a file change matches both, it will be excluded.
When no includes/excludes rules are specified, a **/* pattern will be included by default.
Debugging
Debugging options can be specified on the command line directly:
-
Running in debug mode:
mvn mn:run -Dmn.debug. -
Changing the debugger port:
mvn mn:run -Dmn.debug -Dmn.debug.port=5006. -
Changing the debugger host:
mvn mn:run -Dmn.debug -Dmn.debug.host=192.168.1.8ormvn mn:run -Dmn.debug -Dmn.debug.host=* -
Suspending the debugger execution:
mn:run -Dmn.debug -Dmn.debug.suspend.
Monitoring with JMX
Since 5.1.0, mn:run no longer adds -Dcom.sun.management.jmxremote to the application’s command line, so the JVM
no longer starts the JMX management agent before main on every start and restart.
Local tools do not need it. JConsole, VisualVM and Java Mission Control attach to the running application and start
the local agent on demand, and jcmd does not use the agent.
To start the agent with the application anyway, for example when the JVM runs with -XX:+DisableAttachMechanism,
pass the property yourself in one of these ways:
-
As a user property, which
mn:runpasses on to the application:mvn mn:run -Dcom.sun.management.jmxremote. -
As a JVM argument:
mvn mn:run -Dmn.jvmArgs=-Dcom.sun.management.jmxremote. -
In the plugin configuration:
<configuration> <jvmArguments>-Dcom.sun.management.jmxremote</jvmArguments> </configuration>
For remote JMX, set com.sun.management.jmxremote.port together with the authentication and SSL properties your
setup needs. The JVM then starts the agent, and -Dcom.sun.management.jmxremote is not needed.
Class data sharing (experimental)
On every launch and restart, the JVM parses, verifies and links the classes of all the dependency JARs again. With
mn.classDataSharing, mn:run keeps those classes in a static
CDS archive, and the launches after the first one
map them from the archive instead:
$ mvn mn:run -Dmn.classDataSharing
The option is experimental: its name, its files and its behaviour may change in a later version. It needs JDK 25 or later
to run the application (the toolchain’s JDK when one is configured). With an older JDK, mn:run logs one line and
launches the application as it does without the option.
What it does and what it costs
-
The first launch records the classes it loads (
-XX:DumpLoadedClassList). When that launch ends, because of a restart, because the application exited with status 0, or because of Ctrl+C,mn:runcreates the archive in the background withjava -Xshare:dumpand checks once that the JVM accepts it. This takes a few seconds. Launches in the meantime run without an archive. After Ctrl+C, the nextmn:runcreates the archive in the background instead, and its first launch runs without it. With-Dmn.watch=false, the goal waits for the archive before it ends. -
The launches and restarts after that get
-XX:SharedArchiveFile=… -Xlog:cds*=off,aot*=off, before the arguments ofmn.jvmArgs. They run no extra check. -
The archive is specific to the JDK build, to the JVM options that decide whether the JVM can use an archive (the
-XX:options, the heap size, the module options and the modules that-Dcom.sun.management…properties, JFR options and Java agents add), and to the path, size and modification time of every dependency JAR. When one of them changes, the next launch records again and the archive is created again. Editing the code or the resources of the project never does. -
mn:runadds no-Dcom.sun.management…property of its own, so the archive leaves out the JMX agent’s module (jdk.management.agent) unless you start the agent as the previous section describes. Turning that on or off records again. -
Everything lives in
target/mn-cds: the archive (about 50 MB for an application with 50 dependency JARs), the recorded class list, the list of the files of the JARs, and the log of the dump.mvn cleandeletes it, somvn clean mn:runrecords again. -
If the JVM cannot create or use the archive,
mn:runlogs one line that names the log file, and launches without an archive until one of the inputs above changes.
The archive works with -Dmn.debug and with Java agents, including the native image agent of -Dagent=true.
Class path order
While the option is on, the class path of every launch is:
-
the dependency JARs, in the usual order;
-
the
target/classesdirectories of the project and of the reactor modules it depends on; -
any other dependency entry, such as a directory.
Without the option, the target/classes directories come first. Only the JARs go into the archive, because the JVM does
not archive classes from directories, so editing the project never invalidates the archive.
Before each launch, mn:run looks up every file of the entries that move after the JARs in the files of the JARs. If a
file exists in both, for example a class, a logback.xml or an application.yml, moving the project after the JARs
would change which copy the application sees. mn:run then logs a warning that names the file and the JAR, and
launches with the usual order and no archive. These files are not compared, because they are merged or describe the
JAR they are in: META-INF/services/, META-INF/micronaut/, META-INF/MANIFEST.MF, module-info.class,
META-INF/versions/, META-INF/maven/, signature files, and licence and notice files.
The check cannot see one consequence of the new order: the merged files of the project (META-INF/micronaut/,
META-INF/services/) are now found after those of the dependencies.
-
Micronaut registers an application bean of a type that a library also provides after the library’s bean, so an injected collection of that type that has no explicit order (
@Order,Ordered) can list its beans in another order. -
A library that takes the first service provider it finds can pick another one.
Checking that the archive is used
Log the class loading of a launch that has the archive. The application runs in the target directory, so the file
below is target/class-load.log:
$ mvn mn:run -Dmn.classDataSharing "-Dmn.jvmArgs=-Xlog:class+load:file=class-load.log"
The classes of the dependencies show source: shared objects file, and those of the project show their
target/classes directory.
When the option has no effect
mn:run launches the application as it does without the option, and says why at debug level (-X), when the JVM
options, from mn.jvmArgs or from the JAVA_TOOL_OPTIONS, JDK_JAVA_OPTIONS or _JAVA_OPTIONS environment variables:
-
already decide what CDS or the JDK AOT cache does:
-Xshare:…,-XX:SharedArchiveFile,-XX:SharedClassListFile,-XX:ArchiveClassesAtExit,-XX:+AutoCreateSharedArchive,-XX:DumpLoadedClassList,-XX:AOTCache,-XX:AOTCacheOutput,-XX:AOTMode,-XX:AOTConfigurationor-XX:+AOTClassLinking; -
change the module graph with
--limit-modules,--upgrade-module-path,--patch-moduleor--module-path; -
or read more options from a file (
@argfile,-XX:VMOptionsFile,-XX:Flags).
Other options
For a full list of configuration options, check the mn:run goal documentation.
Collecting native-image metadata during mn:run
mn:run can launch the application with the GraalVM native-image tracing agent by using the same -Dagent=true
switch that org.graalvm.buildtools:native-maven-plugin understands:
$ mvn mn:run -Dagent=true -Dmn.watch=false
This generates metadata under target/native/agent-output/main.
Use this when you want to exercise the application on the JVM and then feed the collected metadata back into the
upstream native-image workflow. Micronaut still delegates native-image packaging to
org.graalvm.buildtools:native-maven-plugin; mn:run only covers the application-run tracing step.
The initial mn:run integration supports the upstream standard agent mode. Advanced workflows such as direct mode,
conditional mode, and metadata copy orchestration should continue to use the upstream native-build-tools Maven plugin
workflow directly.