The Maven Parent

The Gradle plugin's counterpart is a parent POM, spider-silk-maven-parent, whose pluginManagement and profiles carry the same packaging. Maven shares build conventions by inheritance rather than by plugin: a Maven plugin runs goals, and cannot declare other plugins' configuration the way a Gradle plugin can.

<parent>
  <groupId>net.benelog.spidersilk</groupId>
  <artifactId>spider-silk-maven-parent</artifactId>
  <version>1.1.0</version>
</parent>

The parent is published to Maven Central, which Maven searches by default, so the <parent> block needs no repository or settings.xml entry beside it.

Opting in by declaring

Every plugin configuration sits in the parent’s pluginManagement, so a plugin stays inert until the child declares it. The declaration is the opt-in, the way spiderSilk { jte() } is on the Gradle side. A child that containerizes and precompiles jte declares both, and configures only what is the application’s own:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <mainClass>flashcard.FlashcardApp</mainClass>
          </manifest>
        </archive>
      </configuration>
    </plugin>
    <plugin>
      <groupId>com.google.cloud.tools</groupId>
      <artifactId>jib-maven-plugin</artifactId>
      <configuration>
        <to>
          <image>ghcr.io/benelog/spider-silk-flashcard:${spider-silk.image.tag}</image>
        </to>
      </configuration>
    </plugin>
    <plugin>
      <groupId>gg.jte</groupId>
      <artifactId>jte-maven-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>org.graalvm.buildtools</groupId>
      <artifactId>native-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

The main class is declared once, in the jar manifest, where a runnable jar needs it anyway. Jib infers its entrypoint from there, and native-maven-plugin reads the same entry for the binary. Configuring it on Jib and native-maven-plugin separately would also work, until the two copies drift apart.

The declared plugins inherit the conventions the Gradle plugin sets:

  • Jib builds on eclipse-temurin:25-jre and exposes port 8080. mvn package jib:buildTar writes target/jib-image.tar with no Docker daemon involved, layered as Deployment describes.

  • maven.compiler.release is set to 25, which doubles as where Jib reads the Java version it checks against the base image. The targetCompatibility mismatch that the Gradle plugin works around therefore does not arise in Maven.

  • jte precompiles every template under src/main/resources/jte at build time, native-image resources included, exactly as Templates describes.

  • The native binary builds with <fallback>false</fallback>, mostly static (--static-nolibc, so the distroless/base container owes it nothing but glibc), and the reachability metadata repository enabled. It is named ${spider-silk.native.image-name}, which defaults to the artifactId.

Generated JSON codecs

The parent puts silk-json-processor on the compiler’s processor path for every child, the counterpart of spiderSilk { json() }. The compiler plugin runs in every build, so this needs no declaration, and it costs nothing in a child with no type annotated @JsonBound. A child that uses the jakarta.json.bind annotations adds the API itself, in the provided scope, since the annotations are read at compile time and never needed again:

<dependency>
  <groupId>jakarta.json.bind</groupId>
  <artifactId>jakarta.json.bind-api</artifactId>
  <version>3.0.1</version>
  <scope>provided</scope>
</dependency>

Generated JSON Codecs describes what the processor generates.

The native profile

mvn -Pnative package jib:buildTar ships the GraalVM binary instead of the jar. The profile binds native:compile-no-fork to package, swaps Jib’s base for distroless/base, and attaches Jib’s native-image extension pointed at the same binary name. The native image chapter walks through that switch.

The ${spider-silk.image.tag} placeholder in the child’s image name above keeps the two images apart: the parent defaults it to latest, the profile sets it to native, and a child that hard-codes a tag instead opts out of the distinction.

Dockerfile warm-up

Where a platform demands a Dockerfile, mvn dependency:go-offline takes the place of the Gradle plugin’s resolveDependencies. It downloads the dependencies and plugins for the warm-up layer, and runs after copying the pom alone, the way the Gradle Dockerfile copies the build scripts.

What the parent pins

The parent’s version fixes the versions of what it manages: jte 3.2.4, Jib 3.5.2 (the Maven plugin lags the Gradle plugin’s 3.5.4 upstream), the GraalVM build tools 1.1.14, and its own version of silk-json-processor. An upgrade of any of them arrives as a parent release. As on the Gradle side, nothing here is beyond plain Maven: every convention is readable in the parent POM itself, and a build that would rather own a block copies it out and drops the inheritance.