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

io.vidocq.runtime:vidocq-runtime-maven-plugin:0.4.0-SNAPSHOT

Java module

(none) — Maven plugins run on Maven’s class path, so the plugin intentionally has no module-info.java.

Goals

Goal Role

vidocq:generate

Generates the CDI bean index (META-INF/vauban-beans.list) and the code of the dependency jars it scans — their _VaubanComponents, client proxies and intercepted subclasses — into an enriched copy of each jar that every launch uses. See the vidocq:generate goal NEW. Extension discovery itself needs no index — it is plain ServiceLoader over provides directives.

vidocq:package

Standalone distribution: bin/run.sh + bin/run.cmd launchers and lib/*.jar (application + module-path closure), attached as a -dist.zip artifact. -Dvidocq.package.layer=false gives the legacy layout, launched through the main class’s module: see vidocq:package layouts NEW. Never a dev-only jar, even one the project declares: see Dev tools and binaries NEW.

vidocq:dev

Dev/watch mode — forks a child JVM with -Dvidocq.profile=dev, recompiles and hot-reloads on change. The child runs a JDWP agent on 127.0.0.1:5005, see the debug agent. With continuous testing, it also runs the tests after every reload; see Continuous testing. It also adds the dev tools — the dev console and, per extension, its -dev panel module — to the child’s module path only: see Dev tools and binaries NEW.

vidocq:run NEW

Runs the application once in a forked JVM, after compiling and indexing it: the goal forks the lifecycle up to process-classes, so mvn vidocq:run alone is a complete run. See the vidocq:run goal.

vidocq:test NEW

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 vidocq:test goal.

vidocq:checkpom

Verifies every vidocq-runtime-<name>-extension declared in the pom is correctly wired (annotation processor paths, codegen bundles). Bound to validate via pluginManagement. A missing codegen bundle is reported with the <path> to paste into the compiler’s annotationProcessorPaths, its group and version included NEW; a -dev companion module is never asked for one. Also warns, never fails, on a dev-only jar declared in any scope but test: see Dev tools and binaries NEW.

vidocq:analyze-deps NEW

Explains which dependencies vidocq:generate scans, why, and what it does with each; prints the <scanDependencies> block that makes the detection explicit. See vidocq:analyze-deps goal NEW.

vauban:modularize (Vauban plugin)

Gives the non-modular dependency jars a module-info in target/vauban-modularized/, picked up automatically by dev, jlink and package. No longer a goal of this plugin — see modularize moved to the Vauban plugin NEW.

vidocq:jlink

Standalone Java image (target/dist/) with bin/<launcher> binary — no java required on target.

vidocq:jpackage

Native OS bundle (.app macOS, .exe/.msi Windows, .deb/.rpm Linux) or cross-platform app-image.

vidocq:docker

Generates target/Dockerfile distroless based on distroless/base-debian12:nonroot. Optional build via vidocq.docker.build=true.

vidocq:check-module-info

Verifies the application module-info.java declares the JPMS directives its Vidocq extensions need (e.g. opens db.migration for Flyway migrations).

vidocq:complete-module-info

Opt-in companion of check-module-info — rewrites src/main/java/module-info.java to add the missing directives.

vidocq:idea NEW

Experimental — writes one IntelliJ IDEA shared run configuration per application of the reactor into .run/, or checks them in CI. See the vidocq:idea goal.

Scanned dependencies and Java Modules

When vidocq:generate scans a dependency, the classes it produces for it belong to that dependency’s packages. The plugin keeps the module path split-package-free end to end: generate parks those classes in target/vidocq-patches/<artifactId>/, then NEW writes an enriched copy of the jar that carries them and declares its providers (vidocq:generate goal NEW); dev, run, package and jlink use that copy in place of the original. Only a jar that cannot be given a module descriptor keeps its original, its classes attached with --patch-module. Zero configuration. See Usage — Multi-module applications.

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 Vidocq-Scan-Dependencies: <groupId:artifactId patterns> in its manifest. The langchain4j-cdi MCP extension declares dev.langchain4j.cdi.mcp:*, so the server’s beans are generated in every application that uses it.

The application

<scanDependencies>, groupId:artifactId patterns, * accepted at the end of either side.

A CDI bean archive

Any jar with a META-INF/beans.xml whose bean-discovery-mode is not none. On by default; -Dvidocq.generate.autoScan=false or <autoScan>false</autoScan> turns it off.

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

scanDependencies

none

Jars to scan, whatever else selects them; also enriches a signed jar.

scanExcludes

none

Jars never scanned.

autoScan (vidocq.generate.autoScan)

true

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.mainClass already 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

vidocq:modularize

vauban:modularize

vidocq.modularize.*

vauban.modularize.*

target/vidocq-modularized/

target/vauban-modularized/

<release>

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.

<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 nonroot user (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 after vidocq.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

vidocq.mainModule

(required)

The Java module of the application — the same property vidocq:generate and vidocq:package read.

vidocq.mainClass

(unset)

The main class. Optional: the runtime links on the ModuleMainClass attribute of the module descriptor.

vidocq.run.jvmArgs

(empty)

Extra JVM arguments, passed verbatim and split on whitespace.

vidocq.run.args

(empty)

Application arguments, appended after the main module and split on whitespace.

vidocq.run.systemProperties

(empty)

Extra -Dkey=value for the forked JVM, as key=value,key2=value2.

vidocq.run.debug

false

Add a JDWP agent (server=y, address=<host>:<port>) and print its address. Off by default: vidocq:run is also how the application runs in CI and in scripts. See the debug agent.

vidocq.run.debug.port

5005

The port the debug agent listens on.

vidocq.run.debug.host NEW

127.0.0.1

The interface the debug agent listens on: this machine only by default. * or 0.0.0.0 opens it on every interface, with a warning — see the debug agent.

vidocq.run.debug.suspend

false

true suspends the JVM until a debugger attaches, to debug the boot itself.

vidocq.run.gracePeriodMillis

5000

How long the application has to stop after Ctrl+C before it is force-killed.

vidocq.run.skip

false

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 (-Dstyle.color=always, --color=always, a terminal, or an IDE Maven console)

-Dvidocq.console.color=always

not coloured (-Dstyle.color=never, --color=never, -B)

-Dvidocq.console.color=never

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

vidocq.dev.watchDirs

src/main/java,src/main/resources

The main directories to watch.

vidocq.dev.testWatchDirs

src/test/java,src/test/resources

The test directories to watch.

vidocq.dev.debounceMillis

250

How long a burst of saves is collected into one change.

vidocq.dev.devServices

(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

vidocq.dev.continuousTesting

(unset)

Run the tests after every reload: the explicit value, then the application’s files, then on when src/test/java exists.

vidocq.dev.testWatchDirs

src/test/java,src/test/resources

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

vidocq.dev.debug

vidocq.run.debug

true / false

Add the agent (-agentlib:jdwp=transport=dt_socket,server=y,…).

vidocq.dev.debugPort

vidocq.run.debug.port

5005

The port the agent listens on.

vidocq.dev.debugHost NEW

vidocq.run.debug.host NEW

127.0.0.1

The interface the agent listens on.

vidocq.dev.debugSuspend

vidocq.run.debug.suspend

false

true suspends the JVM until a debugger attaches, to debug the boot itself.

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, any 127.x.y.z address or ::1 keep the agent on this machine;

  • *, 0.0.0.0 or :: 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:

  1. 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 has vidocq-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;

  2. for every runtime extension the project resolves, reads its META-INF/vidocq/dev-module descriptor, if it has one, and resolves that companion -dev module and its own dev-only runtime dependencies, plus the console SPI, at the extension’s own groupId and version;

  3. 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;

  4. 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 vidocq:generate. IntelliJ’s Make compiles the module with its own compiler, and vidocq:generate, which completes the bean index with the beans of the dependency jars a build-time annotation processor never saw, is a Maven goal bound to process-classes. An Application run configuration therefore has to carry a Maven step before launch, and anything that bypasses it — a Run from the gutter, a temporary configuration, a module IntelliJ split in two — starts an application whose bean index is incomplete: the server answers 404 (Vidocq/vidocq#83).

A Maven run configuration has no such hole: vidocq:run forks the lifecycle up to process-classes itself, then forks the JVM exactly as the command line does. IntelliJ also disables its own Build step for a Maven configuration, so the IDE build is not even in the picture.

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

.run/McpTimeServerApp.run.xml

A Maven run of vidocq:dev — the existing file, and the existing name: this is the configuration a developer already runs.

.run/McpTimeServerApp (packaged).run.xml

A Maven run of vidocq:run — what the goal wrote before this repository’s #143, unchanged in substance.

.run/McpTimeServerApp (debug).run.xml

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:dev nor vidocq:run needs one; the (debug) file has no before-launch step of its own; it only attaches.

  • JDK — vidocq.idea.jre writes an IntelliJ SDK name (such as temurin-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 — myGeneralSettings is 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 that Remote configurations 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:

  1. its packaging is not pom;

  2. vidocq-runtime-maven-plugin is in its effective <build><plugins> (inherited declarations and active profiles count, <pluginManagement> alone does not): the before-launch step runs vidocq:generate on its pom;

  3. its pom does not set vidocq.idea.exclude to true;

  4. it declares an application main class: the plugin-level <mainClass> if set, otherwise the vidocq.mainClass property. The runtime’s own io.vidocq.runtime.core.Vidocq does not count, and a legacy module/Class value keeps its class — the rule vidocq:package applies.

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

vidocq.idea.configurationName

simple name of the main class

Run configuration name, and file name.

vidocq.idea.moduleName

artifactId

IntelliJ module that holds the main class. Set it when IntelliJ names the module differently, for example <artifactId>.main when it splits main and test sources into two modules, or beta (1) (com.example) when another project has the same artifactId, ignoring case.

vidocq.idea.exclude

false

No run configuration for this module, nor for the modules that inherit its properties.

vidocq.dev.debugHost NEW

127.0.0.1

The host the (debug) file attaches to. Same key vidocq:dev reads for its own debug agent (the debug agent); blank or unset keeps the default.

vidocq.dev.debugPort NEW

5005

The port the (debug) file attaches to, 1-65535. An invalid value is a configuration error, reported with the goal’s other errors, and the default is used.

Goal parameters

Property Default Meaning

vidocq.idea.check

false

true: write nothing; fail when a write would change .run/, and list the files that belong to you as not verified. strict: also fail on those files when their content differs from what the goal writes. Any other value fails the goal.

vidocq.idea.kind NEW

maven

maven: a Maven run configuration of vidocq:run on the application’s pom. application: an Application run configuration of the main class, with Make and vidocq:generate before launch. Any other value fails the goal.

vidocq.idea.generateBeforeLaunch

true

Add the vidocq:generate step before launch. The application kind only.

vidocq.idea.jre

(unset)

IntelliJ SDK name written as the alternative JRE.

vidocq.idea.projectDirectory

directory Maven runs in

The directory IntelliJ opens: .run/ is written there and the $PROJECT_DIR$ paths are relative to it.

vidocq.idea.skip

false

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

vidocq.idea.jre when set, else the Maven runner JRE (Settings > Build, Execution, Deployment > Build Tools > Maven > Runner)

the Maven runner JRE, for the vidocq:generate step

The application

the JVM running Maven, which vidocq:run forks

vidocq.idea.jre when set, else the module SDK (not verified in IntelliJ yet)

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>.main and <name>.test does 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 artifactId Unknown. The goal compares every project of the build, including those -pl leaves 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 set vidocq.idea.moduleName to the name IntelliJ shows.

  • Reload — IntelliJ reloads a changed .run file 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 .run files 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; jlink rejects every automatic module. Give the non-modular jars a descriptor with vauban:modularize in all-automatic mode.

  • 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.logging Handler — declare the module in logging.properties so jlink embeds 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.

Next steps

  • Usage — packaging workflows

  • Reference — configuration keys

  • vidocq/JLINK.md — internal packaging documentation