Until now, the first line of a Vidocq application’s pom was a <parent>: io.vidocq.runtime:vidocq-runtime-parent.
It imported the BOM, set Java 25, put the Vauban indexer on the compiler’s processor path, bound the five executions of
vidocq-runtime-maven-plugin, pinned the versions of the lifecycle plugins and wrote the build identity the startup
banner reads. Convenient, as long as you had no parent of your own.
Many teams do. A company parent carries the release process, the repositories, the enforcer rules, the Sonar settings;
it cannot be swapped for ours. The documented way out was to import vidocq-runtime-bom and declare the plugin with
<extensions>true</extensions>, then copy about sixty lines of build from our parent into your pom: the compiler
configuration, the executions, the plugin versions. It worked (we tried it on a Vaadin application: tests, vidocq:run,
vidocq:dev, a 124 MB jlink image, all green), but every change of the parent left those sixty lines behind, and
nothing told you.
This post describes what lands on main this weekend as 0.4.0-SNAPSHOT (Vidocq/vidocq#380). Nothing of it is
released yet: it will ship with 0.4.0.
The whole pom
A Vidocq application without a parent is an import of the BOM, the plugin as a build extension, one property for the IDE, and the dependencies. Nothing else.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.acme</groupId>
<artifactId>todo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<vidocq.version>0.4.0-SNAPSHOT</vidocq.version>
<!-- Maven and the plugin know it; IntelliJ IDEA reads the language level from the pom alone. -->
<maven.compiler.release>25</maven.compiler.release>
<vidocq.mainModule>com.acme.todo</vidocq.mainModule>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vidocq.runtime</groupId>
<artifactId>vidocq-runtime-bom</artifactId>
<version>${vidocq.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vidocq.runtime</groupId>
<artifactId>vidocq-runtime-core</artifactId>
</dependency>
<dependency>
<groupId>io.vidocq.runtime.extensions.jakartaee.core</groupId>
<artifactId>vidocq-runtime-cassini-rest-extension</artifactId>
</dependency>
<dependency>
<groupId>io.vidocq.runtime</groupId>
<artifactId>vidocq-runtime-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.vidocq.runtime</groupId>
<artifactId>vidocq-runtime-maven-plugin</artifactId>
<version>${vidocq.version}</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
While 0.4.0 is a SNAPSHOT, vidocq create also writes the snapshot repositories, maven.vidocq.dev/snapshots and its
backup, under <repositories> and <pluginRepositories>; they go away with the release.
Add your <parent> above the groupId if you have one: the plugin merges with it. We checked with a parent that
pins its own compiler and Surefire versions and adds -parameters to the compiler: its settings stay, ours are added
next to them, the enforcer it binds runs. On the Vaadin application that started this work, the pom went from 211 lines
to 151, and the Vidocq part from 61 lines to 13.
What the plugin gives the build
<extensions>true</extensions> loads the plugin as a build extension. After Maven has read the projects and before any
goal runs, the plugin looks at the effective model of every project that declares it that way, and adds what
vidocq-runtime-parent would have given, where the model lacks it:
-
the five executions of the plugin:
checkpomatvalidate,test-pathatprocess-test-classes,generateandcheck-module-infoatprocess-classes,packageatpackage, plus the build identity (below); -
Java 25, unless the pom or a parent names a release, a source or a target;
-
the source encoding, UTF-8, unless the pom names one or Maven does (Maven 3.10, which the wrapper
vidocq createwrites runs, sets it in its super POM); -
the compiler argument
-Avauban.providesHint=false, next to the arguments you declared; -
the versions of the lifecycle plugins (compiler 3.15.0, Surefire 3.5.5, jar, resources, clean, install, deploy) when Maven would have used its own defaults, which fail on Java 25 class files under Maven 4;
-
the system property Surefire hands to
@VidocqTest,vidocq.test.runtimePath; -
the switch that makes Eclipse run the annotation processors,
m2e.apt.activation=jdt_apt; -
the processor path of every extension you depend on, as the parent already did.
An execution you bind yourself, whatever its id and phase, is kept and its goal is not bound twice. A version a pom
sets is kept. The second pass changes nothing: a project under vidocq-runtime-parent sees the same effective pom, byte
for byte, as before. The values are the parent’s; a test in the Vidocq reactor fails when the two diverge, so the parent
stays the single source of truth.
mvn help:effective-pom shows the result, and one INFO line at the start of the build says what was added (one line,
wrapped here; the pom above sets the release and Maven 3.10 the encoding, so neither is in it):
[INFO] [vidocq] todo: no Vidocq parent in the pom, the plugin adds execution checkpom, execution test-path,
execution generate, execution check-module-info, execution package, execution build-info,
version maven-compiler-plugin 3.15.0, version maven-surefire-plugin 3.5.5, version maven-jar-plugin 3.4.2, ...,
compiler argument -Avauban.providesHint=false, surefire system property vidocq.test.runtimePath,
property m2e.apt.activation=jdt_apt
The build identity, without a third-party plugin
The parent wrote META-INF/vidocq/build-info/<artifactId>.properties with git-commit-id-maven-plugin: the commit, the
branch, the dirty flag and the build time the startup banner and the dev console show. An application without the parent
gets the same file from a goal of the Vidocq plugin, vidocq:build-info, bound by the plugin like the others. It reads
.git itself for the commit and the branch (a plain repository, a linked worktree, a submodule, a shallow clone, packed
refs) and asks the git command whether the tree is dirty; where there is no repository at all, the build does not
fail: the commit reads unversionned, and the build time is still written. That time is the build’s, to the second,
unless the pom sets a real project.build.outputTimestamp: the 1980-02-01 default Maven 3.10 gives that property is
not taken for one. A project that already runs git-commit-id-maven-plugin, or binds the goal itself, is left alone.
What your IDE sees
We drove the IDEs headless, on copies of the application, with the plugin installed locally.
-
IntelliJ IDEA 2026.2 runs the plugin’s participant at import: the processor path and the executions are the build’s. It does not read the Java release the plugin adds to the model, and shows Java 8 until
maven.compiler.releaseis in the pom. That is why the pom above keeps that one property,vidocq createwrites it, andvidocq:checkpomwarns when it is missing. -
VS Code (Java extension 1.56, jdt.ls 1.61, m2e 2.7.8) derives the language level from the injected release, turns annotation processing on, makes
target/generated-sources/annotationsa source folder and writes the-Avauban.providesHint=falseargument into the project’s APT options. One condition: the language server must run on a JDK 25 (java.jdt.ls.java.home), whichvidocq:vscodewrites, because the server runs the annotation processors in its own JVM and ours are Java 25 class files. On the Java 21 the extension ships, the import succeeds and the build says it cannot load the processors. -
Eclipse 2026-09 has the same m2e and m2e-apt as VS Code’s server. Its default leaves annotation processing off unless the pom asks; the
m2e.apt.activationproperty the plugin injects asks for it, so the project gets its processors without a line of your own. -
Neither IntelliJ nor m2e read
.mvn/maven.configformaven.repo.local: the IDE resolves the plugin from~/.m2, whatever the command line uses.
One limit, unrelated to the parent: a modular Vaadin application does not compile in Eclipse or VS Code, with or
without the parent, because m2e puts the Vaadin automatic modules that module-info does not require explicitly on the
class path, where a named module cannot see them. IntelliJ, which runs javac, compiles it. A separate ticket tracks it.
The CLI: create, convert, check
vidocq create writes a pom without a parent by default from 0.4.0-SNAPSHOT on. --vidocq-version picks the Vidocq
version the project builds on, the CLI’s own by default (--parent-version, its earlier name, still works);
--with-parent writes the previous style, with vidocq-runtime-parent.
A new command converts an existing project in either direction, for the application that gets a company parent, or the one that wants the parent back:
vidocq pom convert --to parentless # removes <parent>, adds the BOM, the plugin as an extension, the release
vidocq pom convert --to parent # the reverse, and removes what the parent now carries
vidocq pom convert --to parentless --check # reports only; exit code 1 when a conversion is needed
It prints the diff before writing, is idempotent, keeps everything else as it is (dependencies, properties, your Vaadin configuration, comments, indentation), and writes nothing when it does not recognise the pom, with a message that says what it did not understand.
vidocq:checkpom learns one check for a project without the parent: the missing maven.compiler.release, for
IntelliJ. The drift detection we first planned for the copied block has no block left to watch.
If you keep the parent
Nothing changes. vidocq-runtime-parent stays published, imports the BOM, binds the same executions and remains the
reference the plugin is tested against. --with-parent writes it; vidocq pom convert --to parent goes back to it.
The parent is now a convenience, not a requirement.
Try it
The change is on main as 0.4.0-SNAPSHOT, published to https://maven.vidocq.dev/snapshots/ and to Central’s
snapshots. With the CLI from the snapshots:
vidocq create --name todo -g com.acme -x cassini-rest --vidocq-version 0.4.0-SNAPSHOT
cd todo
mvn verify # tests, checkpom "all aligned", the first INFO line names what the plugin added
mvn help:effective-pom # the executions and versions, in the effective pom only
mvn vidocq:dev # the usual loop
On an existing project, vidocq pom convert --to parentless --check tells you what would change; drop --check to do
it. Open the result in your IDE and tell us what it does: Vidocq/vidocq#380 is the place.