vidocq-runtime-maven-plugin is the Maven plugin that turns an application into a deployable standalone distribution. It exposes twelve goals: generate, analyze-deps, package, dev, run, checkpom, jlink, jpackage, docker, check-module-info, complete-module-info, idea. (modularize moved to the Vauban plugin — see below.) Also see vidocq/JLINK.md for internal details.
Coordinates
Artefact |
|
Java module |
(none) — Maven plugins run on Maven’s class path, so the plugin intentionally has no |
Goals
| Goal | Role |
|---|---|
|
Generates the CDI bean index ( |
|
Standalone distribution: |
|
Dev/watch mode — forks a child JVM with |
|
Runs the application once in a forked JVM, after compiling and indexing it: the goal forks the lifecycle up to |
|
Continuous testing without the application: runs the tests, then again on every change of the main or test sources, with a summary and keys in the terminal. See the |
|
Verifies every |
|
Explains which dependencies |
|
Gives the non-modular dependency jars a |
|
Standalone Java image ( |
|
Native OS bundle ( |
|
Generates |
|
Verifies the application |
|
Opt-in companion of |
|
Experimental — writes one IntelliJ IDEA shared run configuration per application of the reactor into |
|
Scanned dependencies and Java Modules
When |
vidocq:generate goal NEW
Bound to process-classes. Besides the application’s own bean index, it generates code for the dependency jars it scans, so that their beans run generated code instead of reflection: per package a _VaubanComponents that creates the beans, injects their fields and calls their methods, a client proxy per normal-scoped bean, an intercepted subclass per intercepted one — the same code the Vauban annotation processor writes for a project, made with the Class-File API.
Which jars it scans
| Source | How |
|---|---|
An extension |
A jar of the project declares |
The application |
|
A CDI bean archive |
Any jar with a |
The dependencies considered are the compile-scope and runtime-scope ones: everything the module path holds at run time. A selected jar is left out when it already carries generated code (a Vidocq brick compiled with the Vauban processor), when <scanExcludes> names it, or when it is signed and <scanDependencies> does not name it: the build log says which, and why.
The enriched copy
Each scanned jar gets a copy in target/vidocq-enriched/, under its original file name: the jar with its generated classes, and a module descriptor that provides io.vidocq.vauban.api.VaubanComponentProvider with them and requires the Vauban modules they call. A jar without a descriptor is first given one, an open module synthesized as vauban:modularize does. vidocq:dev, vidocq:run, vidocq:package and vidocq:jlink put the copy on the module path in place of the original.
A copy is written again only when what it is made from changes — the jar, its generated classes, its providers — so a vidocq:dev reload leaves alone the jar its JVM holds open. On a clean build generate runs before vauban:modularize; at packaging, when that goal gave the jar a descriptor, the copy is rebuilt on it, so the module name and openness configured there are the ones shipped.
The copy is a modified third-party jar: its signature files are dropped, and its manifest records Vidocq-Enriched-From (the coordinates) and Vidocq-Enriched-Digest (sha256: of the original). A packaged application ships it; declare it to your SBOM tooling, or turn the detection off. A jar for which no descriptor can be synthesized — a ServiceLoader lookup no explicit module may declare — keeps its original and gets its generated classes through --patch-module, and the build says so. An IDE launch without a Maven step uses the original jars, and the beans fall back to reflection.
| Parameter | Default | Role |
|---|---|---|
|
none |
Jars to scan, whatever else selects them; also enriches a signed jar. |
|
none |
Jars never scanned. |
|
|
Scan every CDI bean archive. |
The dev console’s CDI panel shows the result: a scanned jar’s beans read Class-File or partial in its codegen column, no longer reflection (the CDI panel). A class that only a build-compatible extension makes a bean — a JAX-RS @Provider to which Cassini’s extension gives @Dependent, say — is covered too: generate resolves the runtime-scope dependencies as well as the compile-scope ones, so it runs every extension the application runs.
vidocq:analyze-deps goal NEW
mvn vidocq:analyze-deps prints, for each bean archive and each named dependency, what it is — its discovery mode, an explicit or automatic module, signed or not — what selects or excludes it, and what vidocq:generate does with it: an enriched copy, an open module synthesized first, or nothing and why. It ends with the <scanDependencies> block that makes the detected bean archives explicit, to paste in the pom. It reads the same scanDependencies, scanExcludes and autoScan as vidocq:generate: configure them at plugin level so both goals see them. It changes nothing.
vidocq:package layouts
By default the application jar lands in app/, apart from the module-path closure in lib/, and the
bin/ launchers boot the runtime with -Dvidocq.app.path="$BASEDIR/app": the application resolves into the
module layer the Vauban class loader defines, as it does under vidocq:dev and vidocq:run.
The legacy layout NEW
-Dvidocq.package.layer=false puts everything in lib/ and the launchers start the JVM with --module,
which takes a module, then a class:
-
with no application main class, the runtime’s own main,
io.vidocq.runtime.core/io.vidocq.runtime.core.Vidocq; -
with an application main class,
<vidocq.mainModule>/<vidocq.mainClass>; -
a
vidocq.mainClassalready written<module>/<class>is used as it is.
A plain vidocq.mainClass without vidocq.mainModule fails the build, with a message asking for one or the
other. The launchers used to pass the class name alone to --module, and the JVM stopped on a
FindException before main (Vidocq/vidocq#198).
modularize moved to the Vauban plugin NEW
The vidocq:modularize goal no longer exists. It is now vauban:modularize, in
io.vidocq.vauban:vauban-maven-plugin — Vauban owns the class loader and the module machinery, so
the goal that writes module descriptors belongs with them. What it does is unchanged; the
coordinates, the goal prefix, the property names and the output directory move.
<plugin>
<groupId>io.vidocq.vauban</groupId>
<artifactId>vauban-maven-plugin</artifactId>
<version>${vauban.version}</version>
<executions>
<execution>
<id>modularize</id>
<goals><goal>modularize</goal></goals>
</execution>
</executions>
</plugin>
| Before | After |
|---|---|
|
|
|
|
|
|
|
gone — every multi-release variant is analysed whatever its release |
vidocq:dev, vidocq:jlink and vidocq:package still prefer the patched copies automatically,
reading the new directory. The goal’s own reference is in the Vauban documentation: vauban:modularize in detail.
vidocq:jlink goal
<plugin>
<groupId>io.vidocq.runtime</groupId>
<artifactId>vidocq-runtime-maven-plugin</artifactId>
<executions>
<execution>
<id>jlink</id>
<goals><goal>jlink</goal></goals>
<configuration>
<mainModule>com.example.myapp</mainModule>
<mainClass>com.example.myapp.MainApp</mainClass>
<launcher>my-app</launcher>
<distDir>${project.build.directory}/dist</distDir>
<stripDebug>true</stripDebug>
<compress>zip-6</compress>
<includeResources>
<param>vidocq.properties</param>
<param>logging.properties</param>
</includeResources>
</configuration>
</execution>
</executions>
</plugin>
Typical output:
target/
├── dist/
│ ├── bin/my-app # binary launcher
│ ├── conf/ # overridable config (ExternalFileConfigSource)
│ │ ├── vidocq.properties
│ │ └── logging.properties
│ ├── lib/ # app + JDK Java modules
│ ├── legal/
│ └── release
├── installer/ # if jpackage is enabled
│ └── my-app.app/
└── Dockerfile # if docker is enabled
vidocq:jpackage goal
Reuses the jlink image. Default type: app-image (cross-platform). On macOS, .app. On Windows, .exe or .msi. On Linux, .deb or .rpm.
See vidocq/JLINK.md §3 for the exhaustive parameter list.
vidocq:docker goal
Generates a distroless Dockerfile. Why distroless/base-debian12:nonroot?
-
No shell, no package manager — minimal attack surface.
-
Enforced
nonrootuser (UID 65532). -
Compatible with the strictest image policies.
./mvnw -ntp package -Dvidocq.docker.build=true
docker run --rm -p 8080:8080 example/my-app:1.0.0
# With external config
docker run --rm -p 8080:8080 \
-e VIDOCQ_CONFIG_DIR=/etc/myapp \
-v $(pwd)/conf:/etc/myapp:ro \
example/my-app:1.0.0
NEW The image must be linked on Linux. vidocq:jlink links against the JDK of the machine that runs it, so on macOS
or Windows the image holds a Mach-O or PE bin/java that a Linux container cannot start. The goal reads that
header: when the image is not Linux it logs a warning and still writes the Dockerfile, and with
-Dvidocq.docker.build=true it fails before running docker build. Build the image on Linux, in CI for instance.
vidocq:run goal NEW
vidocq:run runs the application once, in a JVM it forks the way the production launcher does. Before that it
forks the lifecycle up to process-classes, so a single command compiles the module, completes the bean index
with vidocq:generate and starts the application:
mvn vidocq:run # compile, index, run
mvn vidocq:run -Dvidocq.run.debug=true # … with a JDWP agent on 127.0.0.1:5005
mvn vidocq:run -Dvidocq.run.args="--seed data.json" # … with application arguments
mvn vidocq:run -Dvidocq.chappe.listener.default.port=8081
That forked lifecycle is the reason the goal exists. An IDE that builds with its own compiler never runs
vidocq:generate: the bean index then misses the beans of the dependency jars and the server answers 404
(Vidocq/vidocq#83). A Run in the IDE that is a Maven run of this goal cannot — which is what
vidocq:idea now writes.
The forked JVM is the one vidocq:dev forks, without the source watcher, the reload file and
-Dvidocq.profile=dev: the module path of the packaged distribution (every runtime dependency, the enriched
copy of a scanned jar, else the modularized copy of a jar when vauban:modularize produced one, plus the
--patch-module options of a scanned jar that could get no enriched copy), --add-modules ALL-MODULE-PATH, the application classes
handed to the runtime through -Dvidocq.app.path so that they boot in a Vauban-defined module layer, the
project directory as the working directory, and inherited I/O — the application writes straight to Maven’s
console.
The goal blocks until the application exits:
-
Ctrl+C stops it. A shutdown hook sends the equivalent of a
SIGTERM, so the runtime drains as it does in production, and force-kills the JVM only aftervidocq.run.gracePeriodMillis. An interrupted run is not a failed build. -
The exit code is the goal’s result. Zero passes; anything else fails the build with the code in the message, the application’s own output being just above it in the log.
Goal parameters
Every one of them is a property: set it on the command line, or in the module’s <properties> for the value a
run always uses.
| Property | Default | Meaning |
|---|---|---|
|
(required) |
The Java module of the application — the same property |
|
(unset) |
The main class. Optional: the runtime links on the |
|
(empty) |
Extra JVM arguments, passed verbatim and split on whitespace. |
|
(empty) |
Application arguments, appended after the main module and split on whitespace. |
|
(empty) |
Extra |
|
|
Add a JDWP agent ( |
|
|
The port the debug agent listens on. |
|
|
The interface the debug agent listens on: this machine only by default. |
|
|
|
|
|
How long the application has to stop after Ctrl+C before it is force-killed. |
|
|
Skip the goal. |
The properties the application sees
The application runs in another JVM, so a -D of the Maven command line does not reach it by itself. The goal
forwards the ones that configure the application: every -Dvidocq. that is not a setting of this plugin
(vidocq.run., vidocq.dev., vidocq.idea., vidocq.docker., vidocq.jlink., vidocq.mainClass,
vidocq.mainModule, and the other packaging settings). So mvn vidocq:run
-Dvidocq.chappe.listener.default.port=8081 listens on 8081. Anything else goes through
vidocq.run.systemProperties, and the forked JVM inherits Maven’s environment as it is.
NEW vidocq:dev forwards the same set, by the same rule. Before, a -Dvidocq.* of the command line
never reached the JVM it forks: mvn vidocq:dev -Dvidocq.chappe.listener.default.port=8081 still listened on the
default port, silently. A vidocq.dev.systemProperties entry and the goal’s own values (vidocq.profile=dev)
win over the command line, as vidocq.run.systemProperties does for vidocq:run.
Colours in the forked JVM NEW
The forked JVM writes to Maven’s own console, so it takes Maven’s colour policy — vidocq:run and vidocq:dev
both pass it as -Dvidocq.console.color:
| Maven’s own output | The forked JVM |
|---|---|
coloured ( |
|
not coloured ( |
|
nothing asked either way |
nothing is passed: the runtime decides for itself, as it does outside Maven |
This is what brings the colours back in an IDE: IntelliJ runs Maven with -Dstyle.color=always, but its Maven
console is neither a terminal nor an IntelliJ Run console, so the runtime’s auto policy would turn colours
off while Maven’s own [INFO] lines stayed coloured. The signal read is the jansi.mode system property that
Maven sets for itself, with the style.color user property as a fallback.
Two things always win. An explicit -Dvidocq.console.color=… on the Maven command line is not second-guessed:
the goal passes that very value on to the forked JVM, since the property you set belongs to the Maven JVM and
the application runs in another one. And a non-empty NO_COLOR in the environment stops the decision — nothing
is passed, because the runtime honours NO_COLOR on its own, over any mode. The goal says what it decided, and
why, on one -X debug line.
vidocq:test goal NEW
vidocq:test runs the application’s tests on every change, without starting the application: no child JVM,
no dev console, no debug agent. It opens the dev services session, runs every test, then watches the main and
test directories and runs them again on each change. A run is mvn test-compile surefire:test, so a main
change is recompiled by the run itself. See Continuous testing for the whole
behaviour, the results file and the dev services it shares.
mvn vidocq:test
[INFO] Vidocq test — watching [src/main/java, src/main/resources] and [src/test/java, src/test/resources] [INFO] Tests: 41 passed, 1 failed, 0 skipped in 3.2 s (run-all) [INFO] FAILED com.acme.OrderServiceTest#rejectsEmptyCart — AssertionFailedError: expected: <400> but was: (200) [INFO] Log: target/vidocq-dev-tests.log [r] run all [f] rerun failed [q] quit
With a console, r and Enter run every test, f the failed ones, q quits; Ctrl+C always stops the goal,
its run and its dev services.
| Property | Default | Meaning |
|---|---|---|
|
|
The main directories to watch. |
|
|
The test directories to watch. |
|
|
How long a burst of saves is collected into one change. |
|
(unset) |
Whether the goal opens a dev services session for the tests: the explicit value, then the application’s files, then on. |
Continuous testing in vidocq:dev NEW
vidocq:dev gains two parameters:
| Property | Default | Meaning |
|---|---|---|
|
(unset) |
Run the tests after every reload: the explicit value, then the application’s files, then on when |
|
|
The test directories to watch: a change there runs the tests without reloading the application. |
The debug agent of vidocq:dev and vidocq:run NEW
vidocq:dev starts its child JVM with a JDWP agent, and vidocq:run does when -Dvidocq.run.debug=true asks
for one. A JDWP agent executes what the debugger connected to it asks, so whoever can reach its port can run any
code in the application JVM. The agent therefore listens on the loopback interface, 127.0.0.1: a debugger on
the same machine attaches to localhost:5005 as before, and no other machine can connect.
vidocq:dev |
vidocq:run |
Default | Meaning |
|---|---|---|---|
|
|
|
Add the agent ( |
|
|
|
The port the agent listens on. |
|
|
|
The interface the agent listens on. |
|
|
|
|
The host is handed to the agent as it is written, so every spelling the JVM understands works:
-
127.0.0.1(the default),localhost, any127.x.y.zaddress or::1keep the agent on this machine; -
*,0.0.0.0or::open it on every interface; -
any other address or host name opens it on that interface only.
The goal says where the agent listens, and warns as soon as other machines can reach it:
[INFO] Debug agent (JDWP) on port 5005, host 127.0.0.1 — attach any time
[INFO] Debug agent (JDWP) on port 5005, host * (every interface) — attach any time
[WARNING] vidocq.dev.debugHost=* makes the debugger reachable from the network: the JDWP agent listens on every
interface, port 5005, and whoever connects to it can run any code in the application JVM. Leave
vidocq.dev.debugHost at its default, 127.0.0.1, unless every machine that can reach this port is trusted.
Open it only when the debugger really runs elsewhere — a container, a VM, another machine — and only on a network
you trust: mvn vidocq:dev -Dvidocq.dev.debugHost='' (quote the for the shell). An empty host is the default
one, never every interface.
Dev tools and binaries NEW
The dev console and every extension’s -dev panel module (A -dev
module, never packaged) are development tools: only vidocq:dev ever adds them, to the child JVM’s module
path, and no binary this plugin produces ever contains them (Vidocq/vidocq#143). The console’s SPI,
vidocq-runtime-devconsole-spi, is not one: an ordinary API jar, vidocq:dev adds it with the console, and it
ships in a binary whenever an extension that keeps the all-in-one DevConsolePanel form requires it.
What vidocq:dev adds, and when. After building the child’s module path as it always has, the goal:
-
resolves the dev console and its SPI itself, through Maven’s own resolver (Aether), at the Vidocq version of the application’s
vidocq-runtime-chappe-webserver-extension, with a warning when that is not the plugin’s own version, and looks in the plugin repositories as well as the project’s (Vidocq/vidocq#148), the moment the application already hasvidocq-runtime-chappe-webserver-extension: the console serves its page through Chappe, so without it there is nothing to serve the page, and nothing is added; -
for every runtime extension the project resolves, reads its
META-INF/vidocq/dev-moduledescriptor, if it has one, and resolves that companion-devmodule and its own dev-only runtime dependencies, plus the console SPI, at the extension’s owngroupIdand version; -
never adds a module twice: a jar the console step already put on the path is not added again by a companion’s own transitive dependencies, and a jar the project already resolves at runtime scope — the legacy declaration, kept working — is left where it is, not duplicated;
-
logs one INFO line naming what it added:
[INFO] Dev tools: vidocq-runtime-cassini-rest-extension-dev, vidocq-runtime-mansart-pool-extension-dev
An application with no Chappe gets no console, and says so instead of adding one:
[INFO] Dev tools: no dev console, it needs vidocq-runtime-chappe-webserver-extension
Offline, or before the console was ever built locally, costs the console only — a warning, never a failed
goal, and vidocq:dev continues without it:
[WARNING] Dev tools: cannot resolve io.vidocq.runtime.extensions.essentials:vidocq-runtime-devconsole-extension:{project-version} (the dev console); vidocq:dev continues without it. Run the build once online, or mvn -U.
A companion that cannot be resolved the same way costs only that one extension’s live panel — its section keeps its boot facts, unaffected — and a descriptor that cannot be read, or that names more than one artifact or an invalid one, costs the same, naming the jar. Both are warnings, never failures.
vidocq:run, the packaging goals, and never twice. vidocq:run, vidocq:package, vidocq:jlink,
vidocq:jpackage and vidocq:docker add none of this. They also drop every jar marked Vidocq-Dev-Only: true
in its manifest from what they launch or ship — the console, every -dev module, and the dev services
extension — even when the project declares one directly, at any scope but test. Each dropped jar logs one
WARNING per goal run, never fails the build:
[WARNING] vidocq-runtime-devconsole-extension is dev-only: not packaged; remove the dependency, vidocq:dev brings it
The one exception, kept from vidocq:run’s dev services opt-in (Vidocq/vidocq#123): `mvn vidocq:run
-Dvidocq.dev.devServices=true still adds vidocq-runtime-devservices-extension, from the plugin’s own
dependencies, for that one run — it is never packaged either, and it brings no console.
vidocq:checkpom. At validate, it warns — never fails — about a dependency on a dev-only artifact
declared in any scope but test, with the same message as above. The fix is either to remove the dependency
(vidocq:dev brings it on its own) or to move it to test scope.
Tests. mvn test, vidocq:test and the continuous tests of vidocq:dev get no dev tool from the plugin
either: a test that reads the console, or an extension’s live panel, declares it itself, at <scope>test</scope>,
together with the -dev modules it needs. test scope is never packaged, and checkpom accepts it without a
warning. The dev services JUnit host (Vidocq/vidocq#123) is unaffected: it was already a test-scope
dependency.
vidocq:idea. The Dev, packaged and debug configurations it writes are covered under
the vidocq:idea goal.
vidocq:idea goal NEW
Experimental. vidocq:idea writes IntelliJ IDEA shared run configurations, in .run/, per Vidocq application
of the reactor, from what the poms already declare. By default that is three files per application — a
Maven run of vidocq:dev, the configuration a developer already runs day to day (the console, hot reload,
continuous testing), one of vidocq:run suffixed ` (packaged)` — that one still compiles the module,
completes the bean index and starts the application, the three through Maven — and a Remote JVM Debug one
suffixed ` (debug)` that attaches to `vidocq:dev’s own debug agent NEW (Vidocq/vidocq#143); see
Dev tools and binaries.
mvn vidocq:idea # write or update .run/*.run.xml
mvn vidocq:idea -Dvidocq.idea.check=true # CI: fail when .run/ does not match the poms
mvn vidocq:idea -Dvidocq.idea.check=strict # CI: also fail on files you wrote or edited
mvn vidocq:idea -Dvidocq.idea.kind=application # an Application run of the main class instead
|
Why the Maven kind is the default NEW
The IDE build never runs A Maven run configuration has no such hole: |
Run it from the command line, in the directory IntelliJ opens as the project — usually the reactor root —
and commit .run/. Maven resolves the vidocq: prefix from the pom it is launched on: when the plugin is
only declared in the application modules, add it with its version to the root <pluginManagement>, or call
the goal by its coordinates (mvn io.vidocq.runtime:vidocq-runtime-maven-plugin:0.4.0-SNAPSHOT:idea).
What it generates
For an application mcp-time-server whose pom sets vidocq.mainClass, with
<vidocq.idea.jre>temurin-25</vidocq.idea.jre> in the top-level pom, the Maven kind writes three files
(Vidocq/vidocq#143):
| File | Runs |
|---|---|
|
A Maven run of |
|
A Maven run of |
|
A Remote JVM Debug configuration that attaches to `vidocq:dev’s own debug agent. |
The first file (shortened here; it also carries the options IntelliJ leaves at their default, in the order it writes them):
<!-- Generated by vidocq:idea. Run "mvn vidocq:idea" to update it; once edited, it is left alone. sha256:2636c212… -->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="McpTimeServerApp" type="MavenRunConfiguration" factoryName="Maven">
<MavenSettings>
<option name="myGeneralSettings" />
<option name="myRunnerSettings">
<MavenRunnerSettings>
<option name="jreName" value="temurin-25" />
<!-- … -->
</MavenRunnerSettings>
</option>
<option name="myRunnerParameters">
<MavenRunnerParameters>
<option name="goals">
<list>
<option value="vidocq:dev" />
</list>
</option>
<option name="pomFileName" value="mcp-time-server/pom.xml" />
<option name="workingDirPath" value="$PROJECT_DIR$" />
<!-- … -->
</MavenRunnerParameters>
</option>
</MavenSettings>
<method v="2" />
</configuration>
</component>
The (packaged) file is the same shape with vidocq:run in place of vidocq:dev, and its own name in
name="…". The (debug) file is a different type entirely:
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="McpTimeServerApp (debug)" type="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="127.0.0.1" />
<option name="PORT" value="5005" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5005" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>
127.0.0.1 and 5005 are the same defaults as the debug agent's own. Overriding them for this
file is deliberately narrower than overriding the running agent: the goal reads vidocq.dev.debugHost and
vidocq.dev.debugPort from the module’s own pom <properties> only, never from the command line — a -D for
either is ignored, with a warning, the same treatment vidocq.idea.configurationName and the other per-module
settings already get (Which modules are applications), so that the committed file does not
depend on how vidocq:idea was invoked. There is no vidocq.idea.* prefix on these two: they name the debug
agent itself, which vidocq:dev and vidocq:idea both read the same way.
-
Before launch — nothing on any of the three files: IntelliJ disables its Build step for a Maven run configuration, and neither
vidocq:devnorvidocq:runneeds one; the(debug)file has no before-launch step of its own; it only attaches. -
JDK —
vidocq.idea.jrewrites an IntelliJ SDK name (such astemurin-25) as the Maven runner JRE for the first two files, which is also the JDK the application runs on: the forked JVM comes from the JVM running Maven. Declare it in the top-level pom, since that name must exist on every machine; an absolute path is accepted with a warning. Without it, IntelliJ runs Maven on the JDK of its Maven settings, which can be another one than the JDK you build and test with, and the goal warns. The(debug)file has no JDK option: it only attaches to a JVM already running. -
Maven settings —
myGeneralSettingsis left empty on purpose, on the two Maven files. A run configuration that carries its own replaces the user’s Maven settings (settings file, local repository, offline mode, threads) with what the file holds, and those belong to the machine, not to the repository. -
Format — the one IntelliJ writes itself: the element order, the options left at their default and the 2-space indentation of
XmlSerializer, LF, no XML declaration. Measured against IntelliJ IDEA 2026.2.3’s own serialiser — a generated file deserialised and serialised back comes out identical — so IntelliJ has no reason to rewrite it, and the marker it carries survives. The(debug)file carries no marker comment of its own kind thatRemoteconfigurations do not have; its ownership and drift checks are the same as the other two. -
File name — the configuration name (
McpTimeServerApp,McpTimeServerApp (packaged),McpTimeServerApp (debug)) with the characters IntelliJ replaces in file names (/ \ ? < > : * | "and control characters) turned into_, truncated to 255 characters. Two names that differ only by case fail the goal: they are one file on macOS and Windows.
The application kind
-Dvidocq.idea.kind=application writes what the goal wrote before: one Application run configuration per
application, of the main class, with two before-launch steps — IntelliJ’s Make, then vidocq:generate on the
application’s own pom. Unlike the Maven kind’s three files, there is only ever this one: the application runs
in the JVM IntelliJ starts directly, never under vidocq:dev, so it never has the dev console or any -dev
module NEW (Dev tools and binaries), and there is no (debug) file to attach a
separate debugger to — attach IntelliJ’s own debugger to this configuration instead.
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="McpTimeServerApp" type="Application" factoryName="Application">
<option name="ALTERNATIVE_JRE_PATH" value="temurin-25" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="io.vidocq.tools.lc4jcdi.mcptimeserver.McpTimeServerApp" />
<module name="mcp-time-server" />
<method v="2">
<option name="Make" enabled="true" />
<option name="Maven.BeforeRunTask" enabled="true" file="$PROJECT_DIR$/mcp-time-server/pom.xml" goal="vidocq:generate" />
</method>
</configuration>
</component>
Apart from the marker line, this is the configuration verified in IntelliJ IDEA 2026.2: Run on the main class
built the module, completed the bean index and started the application. It runs the application in the JVM
IntelliJ starts, which is faster to launch, but every Run that bypasses the before-launch step starts it with
an incomplete bean index. When a single execution of the plugin that runs generate carries a configuration of
its own (for example <scanDependencies> on an execution rather than at plugin level), the step is written
vidocq:generate@<execution id>: a goal run on its own would not see that configuration. With several such
executions it stays vidocq:generate and the goal warns. -Dvidocq.idea.generateBeforeLaunch=false leaves the
step out; it does not concern the Maven kind. Here vidocq.idea.jre is the alternative JRE of the
configuration, and without it IntelliJ launches the application on the module SDK.
The goal never writes .idea/, *.iml or any other project file — those belong to IntelliJ’s own Maven
import — nor run configuration templates, nor Eclipse launch configurations. It never deletes a file, and it
refuses to run when a pom binds it to a lifecycle phase.
Which modules are applications
A project of the build gets a run configuration when:
-
its packaging is not
pom; -
vidocq-runtime-maven-pluginis in its effective<build><plugins>(inherited declarations and active profiles count,<pluginManagement>alone does not): the before-launch step runsvidocq:generateon its pom; -
its pom does not set
vidocq.idea.excludetotrue; -
it declares an application main class: the plugin-level
<mainClass>if set, otherwise thevidocq.mainClassproperty. The runtime’s ownio.vidocq.runtime.core.Vidocqdoes not count, and a legacymodule/Classvalue keeps its class — the rulevidocq:packageapplies.
A <mainClass> set on an execution (for example the jlink one) is not read; when it is the module’s only main
class, the goal warns. These values
are read from each module’s pom, never from the command line: a -Dvidocq.mainClass, -Dvidocq.mainModule,
-Dvidocq.idea.configurationName, -Dvidocq.idea.moduleName, -Dvidocq.idea.exclude, -Dvidocq.dev.debugHost
or -Dvidocq.dev.debugPort NEW is ignored with a warning, so that the committed files do not depend
on how Maven was invoked.
| Module pom property | Default | Meaning |
|---|---|---|
|
simple name of the main class |
Run configuration name, and file name. |
|
|
IntelliJ module that holds the main class. Set it when IntelliJ names the module differently, for example
|
|
|
No run configuration for this module, nor for the modules that inherit its properties. |
|
|
The host the |
|
|
The port the |
Goal parameters
| Property | Default | Meaning |
|---|---|---|
|
|
|
|
|
|
|
|
Add the |
|
(unset) |
IntelliJ SDK name written as the alternative JRE. |
|
directory Maven runs in |
The directory IntelliJ opens: |
|
|
Skip the goal. |
Maven evaluates them against the top-level project of the build — the first selected project when -pl leaves
the root out. That is why no goal property is also a module setting: vidocq.idea.skip in the pom of the first
selected module would skip the goal for every application. To leave a module out, set vidocq.idea.exclude in
its pom; a module pom that sets vidocq.idea.skip gets a warning. When the goal is skipped, it says where
vidocq.idea.skip came from: the command line, a system property, or the properties of the top-level project.
Files that belong to you
The goal owns a file only while its marker matches its content. IntelliJ drops the marker when it saves a configuration you edited, so an edited file becomes yours.
File in .run/ |
mvn vidocq:idea |
-Dvidocq.idea.check=true |
-Dvidocq.idea.check=strict |
|---|---|---|---|
absent |
created |
fails |
fails |
marker valid, content as expected |
unchanged, not rewritten |
passes |
passes |
marker valid, other content |
updated |
fails, with a line diff |
fails, with a line diff |
no marker, content as expected |
marker added |
fails |
fails |
marker whose hash no longer matches (edited since) |
left untouched, warning |
not verified, warning |
fails with a line diff, unless the content is as expected |
no marker, other content (written by hand, saved by IntelliJ) |
left untouched, warning |
not verified, warning |
fails, with a line diff |
A file of yours that pins a JDK while vidocq.idea.jre is not set is a special case: a regenerated file would
lose the pin, so the goal tells you to declare vidocq.idea.jre with that SDK name first, and adopts the file as
it is when nothing else differs. This is how a hand-written configuration moves to the goal without a change.
The default check passes when your files are the only difference, so that a configuration you customised on
purpose does not break CI; its summary is then a warning that names each file it did not verify. Use strict when
every file in .run/ must follow the poms. Line endings and a trailing newline are not edits. To get a generated
file back, delete yours and run the goal again. A marked file that no application maps to any more is reported and
never deleted.
Multi-module builds
vidocq:idea is an aggregator: it runs once, for every project of the build, and writes a single .run/ at
the project directory. Configuration errors (a main class that is not a Java class name, a pom outside the
project directory, a blank name) and name collisions fail the goal before anything is written.
When it is launched inside a module of a larger reactor — a directory that an ancestor pom.xml lists in its
<modules> — the goal fails: IntelliJ loads .run files anywhere in the project content, and paths written
relative to the module would resolve against the reactor root. Run it from the root, or set
-Dvidocq.idea.projectDirectory when IntelliJ really opens the module. A project directory that is not the
directory of a project of the build is accepted with a warning: IntelliJ only loads .run files inside the
project content.
With -pl or -rf, only the selected applications are written or checked, and the goal says so. Profiles
count: run the goal and its CI check with the same active profiles.
JDK
The Vidocq plugin and runtime are compiled for Java 25, and the JDK that runs each step is IntelliJ’s business,
not the file’s — except where vidocq.idea.jre says otherwise.
| Step | Maven kind | application kind |
|---|---|---|
Build |
none (IntelliJ disables its Build step) |
Make, on the module SDK — the project SDK unless the pom declares a JDK toolchain |
Maven |
|
the Maven runner JRE, for the |
The application |
the JVM running Maven, which |
|
Each of them must be JDK 25 or newer. The goal cannot see IntelliJ’s SDKs and prints this reminder after a write.
IntelliJ limits
-
Module names — IntelliJ names an imported module after its
artifactId, but a renamed module, a name clash or a module split into<name>.mainand<name>.testdoes not match. An unknown name fails at Run with "Module '<name>' doesn’t exist in the project"; a split module silently gets a new temporary configuration without the Maven step. The goal warns when the pom shows a known split trigger (different main and test compiler levels, test compiler arguments, different compile and test-compile arguments or toolchains), and when a first import would name the module otherwise: IntelliJ numbers every module whose artifactId equals another one ignoring case, in any group (beta (1) (com.example),Beta (2) (com.vendor)), and uses the directory name for the artifactIdUnknown. The goal compares every project of the build, including those-plleaves out. A module imported before keeps its earlier name, so the name can differ between machines: prefer artifactIds that differ by more than case, or setvidocq.idea.moduleNameto the name IntelliJ shows. -
Reload — IntelliJ reloads a changed
.runfile by itself, but may miss files in a new.run/directory or several files written at once. When a configuration does not appear, use File > Reload All from Disk. -
Same name — a temporary or local configuration with the same name, created earlier from the gutter, can shadow the shared one. Delete it from the Run widget.
-
First open — IntelliJ loads
.runfiles asynchronously; a Run clicked before indexing ends may create a temporary configuration.
External override (ExternalFileConfigSource)
vidocq.properties placed in ${java.home}/conf/ (jlink image) or pointed to by $VIDOCQ_CONFIG_DIR is read with ordinal 250. See Reference for the full hierarchy.
Prerequisites and limitations
-
Strict Java Modules — every dependency must be a named module;
jlinkrejects every automatic module. Give the non-modular jars a descriptor withvauban:modularizeinall-automaticmode. -
Records serialised over REST — Champollion handles them via APT, not reflection. If an external dependency exposes records that are not generated, declare a JSON-B
Converter<T>. -
Custom
java.util.loggingHandler — declare the module inlogging.propertiessojlinkembeds it (handlers=com.example.MyHandler). -
macOS app-image — the bundle requires a valid
--app-version; the plugin defaults to the project Maven version.
See vidocq/JLINK.md §6 and §8 for the full list of known limitations.