Build and Tooling
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
The JDK ships a complete set of command-line tools for compiling, running, packaging, inspecting, and
documenting code. Real projects then drive those tools through a build system — Maven or Gradle — that adds dependency management, a standard directory layout, and a repeatable lifecycle. This page
covers the core tools, then both build systems, then javadoc. References:
the JDK tool specifications,
dev.java "The Core JDK Tools",
the Maven guides, and
the Gradle user guide.
The Core JDK Tools
javac compiles .java to
.class; java launches a class,
a module, or — since Java 11 — a single source file directly;
jar packages classes and can mark
one as the entry point via the manifest’s Main-Class.
javac -d out $(find src -name '*.java') # compile the tree into out/
java -cp out com.example.Main # launch a class from the classpath
java src/com/example/Scratch.java # run one source file, no explicit compile
jar --create --file app.jar --main-class com.example.Main -C out .
java -jar app.jar # executable JAR: Main-Class from the manifest
The remaining tools inspect and package:
jshell # interactive REPL for snippets
jdeps --module-path libs app.jar # static class/module dependency analysis
jlink --add-modules com.example.app --output runtime # minimal custom runtime image
jpackage --name MyApp --input target --main-jar app.jar # native installer / app image
jcmd <pid> Thread.print # ad-hoc diagnostics on a live JVM
jstack <pid> # thread dump
jmap -histo <pid> # heap object histogram
java -XX:StartFlightRecording=duration=60s,filename=rec.jfr -jar app.jar # JDK Flight Recorder
jlink and jpackage operate on modules — see
Packages and Modules. JDK Flight Recorder writes a
low-overhead .jfr profile that JDK Mission Control or jfr print can open.
Building with Maven
Maven expects a fixed layout — src/main/java for code, src/test/java for tests — and a single
pom.xml describing the project. Every artifact has coordinates: groupId : artifactId : version.
See Introduction to the
POM.
myapp/
pom.xml
src/
main/java/com/example/App.java
test/java/com/example/AppTest.java
target/ (build output: classes, test reports, the packaged JAR)
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope> <!-- on the test classpath only -->
</dependency>
</dependencies>
</project>
The build lifecycle is a fixed sequence of phases; running one runs every earlier phase. See Introduction to the Build Lifecycle.
mvn compile # src/main/java -> target/classes
mvn test # compile, then run tests via the Surefire plugin
mvn package # assemble target/myapp-1.0.0.jar
mvn install # copy that artifact into the local ~/.m2 repository
mvn -q clean package # quiet, starting from a clean target/
Building with Gradle
Gradle uses a build script — build.gradle (Groovy DSL) or build.gradle.kts (Kotlin DSL) — that is
executable code rather than declarative XML. The java plugin adds the same src/main/java layout and
the compileJava, test, and jar tasks. See
The Java Plugin.
plugins {
id 'java'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
./gradlew build # compile, test, and assemble
./gradlew test # run tests only
./gradlew --offline classes
In one line: Maven is declarative and convention-bound, so builds look alike and are easy to read; Gradle is a programmable build with fine-grained incremental execution, trading some uniformity for flexibility and speed.
Common Build Plugins
The bare Maven and Gradle setups above compile, test, and package — but real projects add plugins for
testing, code coverage, static analysis, reporting, and publishing. Maven binds each plugin goal to a
lifecycle phase so it runs
automatically; Gradle applies each as a plugin that contributes tasks, most of which the check task then
depends on. Almost every plugin below has a first-class counterpart on the other side, so a project can move
between build systems without losing a capability. For a fuller, Spring-oriented treatment of the coverage and
static-analysis trio — with multi-module report aggregation and generated-source exclusions — see
Maven Quality Plugins.
Unit tests: Surefire / the test task
Maven Surefire runs unit tests in the test phase.
By default it picks up **/Test*.java, **/*Test.java, **/*Tests.java, and **/*TestCase.java;
<includes>/<excludes> override that. Gradle’s core java plugin already provides the
test task — only useJUnitPlatform() is needed
to select the JUnit 5 engine.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/*IT.java</exclude> <!-- integration tests run in the Failsafe phase -->
</excludes>
</configuration>
</plugin>
tasks.named('test') {
useJUnitPlatform()
}
Integration tests: Failsafe / a separate test suite
Integration tests — ones that start a container, hit a database, or bind a port — are slower and more fragile
than unit tests, so they are kept out of the fast test phase and named *IT rather than *Test.
Maven Failsafe is a sibling of Surefire that binds
its integration-test and verify goals to the matching phases and matches **/IT*.java, **/*IT.java,
and **/*ITCase.java; keeping the two goals separate lets verify fail the build only after
post-integration cleanup has run. Gradle has no Failsafe: the current approach is a dedicated suite via the
(incubating) JVM Test Suite plugin.
check does not depend on a custom suite automatically — wire it in explicitly.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
plugins {
id 'java'
id 'jvm-test-suite'
}
testing {
suites {
integrationTest(JvmTestSuite) {
dependencies {
implementation project()
}
targets.configureEach {
testTask.configure {
shouldRunAfter(test)
}
}
}
}
}
tasks.named('check') {
dependsOn(testing.suites.integrationTest)
}
Writing the tests themselves — JUnit 5 and Mockito — is covered in Testing.
Code coverage: JaCoCo
JaCoCo instruments classes at test-run time and reports
line and branch coverage as HTML/XML/CSV. In Maven, prepare-agent attaches the coverage agent to the JVM
Surefire forks, report renders the collected data, and check compares it against configured rules — turning coverage into a build gate rather than a report nobody reads. Gradle’s core
jacoco plugin provides jacocoTestReport and
jacocoTestCoverageVerification for the same two jobs.
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
<execution>
<id>check</id>
<phase>verify</phase>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
plugins {
id 'java'
id 'jacoco'
}
jacocoTestReport {
reports {
xml.required = true
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.80
}
}
}
}
tasks.named('check') {
dependsOn(jacocoTestCoverageVerification)
}
Static analysis: Checkstyle, PMD, SpotBugs
The three tools inspect different artifacts. Checkstyle
reads the source text and enforces formatting and naming conventions against a rule set such as
google_checks.xml. PMD analyses the source structure
for questionable constructs (unused variables, over-complex methods) and bundles CPD, a copy-paste detector.
SpotBugs works on compiled bytecode, matching known
bug patterns such as null dereferences and resource leaks. Each has a Maven plugin whose check goal binds to
verify, and a Gradle plugin — core checkstyle
and pmd, plus the community
com.github.spotbugs plugin — all wired into check.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<configLocation>google_checks.xml</configLocation>
</configuration>
<executions>
<execution><phase>verify</phase><goals><goal>check</goal></goals></execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.27.0</version>
<executions>
<execution><phase>verify</phase><goals><goal>check</goal><goal>cpd-check</goal></goals></execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.3.2</version>
<executions>
<execution><phase>verify</phase><goals><goal>check</goal></goals></execution>
</executions>
</plugin>
plugins {
id 'checkstyle'
id 'pmd'
id 'com.github.spotbugs' version '6.5.11'
}
checkstyle {
toolVersion = '10.21.0' // pins the analyzer itself; bump to the current release
// rule set read from config/checkstyle/checkstyle.xml by default
}
pmd {
toolVersion = '7.10.0'
ruleSets = ['category/java/bestpractices.xml']
}
spotbugs {
toolVersion = '4.9.3'
}
toolVersion pins the analyzer Gradle downloads, independent of the plugin version — keep it on the tool’s
latest release (the Maven plugins pull a sensible default, so they omit the equivalent). By default the Gradle
Checkstyle/PMD/SpotBugs tasks fail the build on any violation; set ignoreFailures = true (or, on the Maven
side, <failOnViolation>false</failOnViolation> / <failOnError>false</failOnError>) to run a tool in
report-only mode while a codebase catches up.
Continuous inspection: SonarQube
The plugins above run each tool in isolation; SonarQube (the
self-hosted server or the hosted SonarQube Cloud) instead collects coverage, duplication, and its own rule
engine’s findings into one dashboard with history and a pass/fail quality gate. A scanner run is a separate
step after verify, not part of it, so it usually runs only in CI: mvn verify sonar:sonar via the
SonarScanner
for Maven, or ./gradlew build sonar via the
SonarScanner
for Gradle (org.sonarqube). The scanner reads the JaCoCo XML report, so run coverage first.
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>5.7.0.6970</version>
</plugin>
plugins {
id 'org.sonarqube' version '7.4.0.8496'
}
sonar {
properties {
property 'sonar.projectKey', 'com.example:myapp'
property 'sonar.host.url', 'https://sonarqube.example.com'
}
}
Pass sonar.token (and, for SonarQube Cloud, sonar.organization) on the command line or via environment
variables — never commit it.
API docs: the Javadoc plugin
The JDK javadoc tool (covered below) generates the HTML; the build plugin is a thin
wrapper that runs it inside the build and, importantly, produces a -javadoc.jar for publishing to a
repository alongside the main artifact.
maven-javadoc-plugin's jar goal is bound to package
for exactly that; Gradle’s core java plugin already defines a javadoc task, and
java { withJavadocJar() } adds the publishable
jar (with withSourcesJar() as its companion).
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.12.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
plugins {
id 'java'
}
java {
withJavadocJar()
}
tasks.named('javadoc') {
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
Report aggregation: Maven Site
mvn site runs the Maven Site plugin, which renders a
static project website gathering every <reporting> plugin’s output — Surefire test results, the JaCoCo
coverage report, Checkstyle/PMD/SpotBugs findings, the Javadoc — into one browsable place. Gradle has no
unified equivalent: each plugin writes its own HTML under build/reports/, and the closest aggregator is the
legacy core build-dashboard plugin,
whose buildDashboard task links together reports other tasks have already produced. It is not run by check
or build, and applying it disables the Gradle configuration cache.
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>3.6.0</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.12.0</version>
</plugin>
</plugins>
</reporting>
plugins {
id 'java'
id 'build-dashboard'
}
tasks.named('test') {
reports {
html.required = true
}
}
// ./gradlew build buildDashboard -> build/reports/buildDashboard/index.html
Publishing to Maven Central
Releasing a library to Maven Central means uploading the main JAR plus its
-sources and -javadoc JARs and their PGP signatures to the Sonatype
Central Portal. On the Maven side the
central-publishing-maven-plugin replaces the
deploy goal and handles the upload and (optionally) the release; pair it with maven-gpg-plugin for the
signatures and the Javadoc/source plugins' jar goals. Gradle’s core
maven-publish only builds the publication — for the Central Portal upload the common choice is the third-party
com.vanniktech.maven.publish plugin, which also
wires in signing and the source/Javadoc JARs.
The Central Portal validates every upload against required POM metadata (name, description, URL, licenses,
developers, SCM), not just the plugin declaration — a pom.xml/build.gradle.kts that omits any of these
fails the release with a metadata-validation error rather than an upload error. A minimal pom.xml covering the
whole flow — coordinates, required metadata, the publishing plugin, the source/Javadoc jars Central Portal
also requires, and GPG signing split into its own Maven profile so an ordinary local mvn verify doesn’t need a
signing key — looks like:
<project>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>A short description of the library</description>
<url>https://github.com/example/myapp</url>
<licenses>
<license>
<name>The Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>Jane Doe</name>
<email>jane@example.com</email>
<organizationUrl>https://github.com/example</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:example/myapp.git</connection>
<developerConnection>scm:git:git@github.com:example/myapp.git</developerConnection>
<url>git@github.com:example/myapp.git</url>
</scm>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.11.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId> <!-- credentials from settings.xml -->
<autoPublish>true</autoPublish>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals><goal>jar-no-fork</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals><goal>jar</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- kept as its own profile: signing needs a GPG key, which a plain "mvn verify" shouldn't require -->
<profiles>
<profile>
<id>sign</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.8</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals><goal>sign</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Signing is opt-in via -P sign precisely so day-to-day builds don’t need a GPG key on hand — only a release
build activates it: mvn -P sign deploy.
The Gradle equivalent uses com.vanniktech.maven.publish’s own DSL for both the source/Javadoc jars and the POM
metadata, instead of separate plugins. Unlike every other Gradle example on this page, this one is shown in the
Kotlin DSL (`build.gradle.kts) rather than Groovy — com.vanniktech.maven.publish’s own docs default to it,
and it’s the form newly generated Gradle projects use; the same `mavenPublishing { } block works unmodified in
a Groovy build.gradle, just with id 'java-library'-style plugin syntax instead of id("java-library").
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.JavaLibrary
plugins {
id("java-library")
id("com.vanniktech.maven.publish") version "0.34.0"
}
group = "com.example"
version = "1.0.0"
mavenPublishing {
configure(JavaLibrary(
javadocJar = JavadocJar.Javadoc(),
sourcesJar = true,
))
publishToMavenCentral()
signAllPublications()
coordinates("com.example", "myapp", "1.0.0")
pom {
name.set("myapp")
description.set("A short description of the library")
inceptionYear.set("2024")
url.set("https://github.com/example/myapp/")
licenses {
license {
name.set("Apache License 2.0")
url.set("https://github.com/example/myapp/blob/main/LICENSE")
distribution.set("https://github.com/example/myapp/blob/main/LICENSE")
}
}
developers {
developer {
id.set("jdoe")
name.set("Jane Doe")
email.set("jane@example.com")
url.set("https://github.com/example/")
}
}
scm {
url.set("https://github.com/example/myapp/")
connection.set("scm:git:github.com/example/myapp.git")
developerConnection.set("scm:git:ssh://github.com/example/myapp.git")
}
}
}
JavaLibrary is the variant for a plain JVM library. An Android library instead passes
configure(AndroidSingleVariantLibrary(variant = "release", sourcesJar = true, publishJavadocJar = true)) — see the Kotlin reference’s own pointer to this section for the JVM/Maven Central side of an Android/Kotlin
library, which otherwise follows the exact same plugin, coordinates(), and pom { } shape shown here.
|
Creating a Sonatype Central Portal Account
Publishing requires an account at central.sonatype.com and a verified
namespace — the groupId prefix a publisher is allowed to release under. A namespace based on a domain the
publisher owns (com.example) is verified by adding a DNS TXT record with a token the portal provides; a
namespace of the form io.github.<user> is verified automatically instead, by proving ownership of the
matching GitHub account — the common choice for an individual maintainer with no domain of their own.
Generating a User Token
Once the account and namespace are set up, Account → Generate User Token on the Central Portal produces a
token pair (a username and a password-like token) used for the actual upload — this is not the account’s own
login password. On the Maven side it goes into ~/.m2/settings.xml, matching the publishingServerId the
plugin configuration references:
<settings>
<servers>
<server>
<id>central</id>
<username>GENERATED_TOKEN_USERNAME</username>
<password>GENERATED_TOKEN_PASSWORD</password>
</server>
</servers>
</settings>
On the Gradle side, com.vanniktech.maven.publish reads the same pair from gradle.properties (local
development) or environment variables (CI):
# gradle.properties -- keep this file out of version control, or use env vars in CI instead
mavenCentralUsername=GENERATED_TOKEN_USERNAME
mavenCentralPassword=GENERATED_TOKEN_PASSWORD
Generating and Publishing a GPG Key
The Central Portal requires every artifact to carry a valid PGP signature, and that the public key be resolvable from a public keyserver so anyone downloading the artifact can verify it:
gpg --gen-key # create a new keypair, interactively
gpg --list-secret-keys --keyid-format long # find the key id to export/publish
gpg --export-secret-keys <KEY_ID> > private-key.gpg # for use in CI, see below
gpg --keyserver keyserver.ubuntu.com --send-keys <KEY_ID> # publish the PUBLIC key
Sonatype’s own validation queries keyserver.ubuntu.com (and mirrors such as keys.openpgp.org) when a
release is uploaded, so the public key must be published before the first deploy.
A GitHub Actions Release Workflow
Following the same style as the C# reference workflow (csharp/build-and-tooling.adoc, == Continuous
Integration): a build job that runs the ordinary build/test steps, and a publish step gated to run only on a
version tag. actions/setup-java itself generates ~/.m2/settings.xml (matching the <server><id>central</id>
block shown earlier) and imports the signing key into an isolated keyring when given the right inputs — there’s
no need for a hand-written settings file or a separate gpg --batch --import step:
name: build
on:
push:
branches: [ main ]
tags: [ 'v*' ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '25'
cache: 'maven'
server-id: central # matches <publishingServerId>central</publishingServerId>
server-username: CENTRAL_TOKEN_USERNAME # env var name -- populated below, not the value itself
server-password: CENTRAL_TOKEN_PASSWORD # env var name -- populated below, not the value itself
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: GPG_PASSPHRASE # env var name -- populated below, not the value itself
- name: Build and test
run: mvn -B verify
- name: Publish to Maven Central
if: startsWith(github.ref, 'refs/tags/v')
run: mvn -B -P sign deploy -DskipTests
env:
CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
-P sign activates the signing profile shown earlier — deploying without it fails Central Portal’s signature
check — and -DskipTests avoids re-running the suite mvn verify already executed a few steps earlier.
server-username/server-password/gpg-passphrase name the environment variables the generated settings.xml
reads from (via gpg.passphraseEnvName, which needs maven-gpg-plugin 3.2.0+ — already satisfied by the 3.2.8
used above); the actual secret values are only ever supplied through the deploy step’s own env: block, never
inlined into a command-line flag.
The Gradle equivalent replaces the last two steps with ./gradlew publish, which com.vanniktech.maven.publish
picks up automatically from ORG_GRADLE_PROJECT_mavenCentralUsername/ORG_GRADLE_PROJECT_mavenCentralPassword
for the Central Portal credentials, and ORG_GRADLE_PROJECT_signingInMemoryKey/
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword for an in-memory signing key — Gradle’s own CI-friendly
mechanism, distinct from the keyring actions/setup-java manages for the Maven job above.
Also worth knowing
Dependency freshness. versions-maven-plugin's
versions:display-dependency-updates and versions:display-plugin-updates goals report newer releases of the
dependencies and plugins a build pins. The Gradle counterpart is
io.github.ben-manes.versions and its
dependencyUpdates task — the plugin’s namespace moved from com.github.ben-manes in v0.55.0, so the old id
still resolves but logs a deprecation warning.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.21.0</version>
</plugin>
plugins {
id 'io.github.ben-manes.versions' version '0.61.0'
}
Fat / uber JARs. To bundle every dependency into one runnable JAR,
maven-shade-plugin's shade goal (bound to package)
merges and optionally relocates the dependency classes; Gradle’s
com.gradleup.shadow (the maintained successor to
com.github.johnrengelman.shadow) adds the shadowJar task.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
plugins {
id 'java'
id 'com.gradleup.shadow' version '9.6.1'
}
To hard-fail a build on banned dependencies, a wrong JDK, or unresolved version conflicts, Maven uses
maven-enforcer-plugin's enforce goal; Gradle has
no single-plugin analogue — the same guarantees come from
dependency constraints and a resolution
strategy such as failOnVersionConflict().
Groovy in a Maven build. Maven has no scripting of its own, so a build step that needs real logic (patching a
generated file, a conditional check) reaches for gmavenplus-plugin — the maintained successor to the old groovy-maven-plugin/GMaven. Its execute goal runs an inline or external
Groovy script bound to a phase; it also compiles src/main/groovy and can generate GroovyDoc. Gradle needs no
equivalent: build scripts are already Groovy (or Kotlin), and the core
groovy plugin compiles Groovy sources
directly.
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>5.1.0</version>
<executions>
<execution>
<goals>
<goal>execute</goal>
</goals>
</execution>
</executions>
<configuration>
<scripts>
<script>file:///${project.basedir}/src/build/patch-version.groovy</script>
</scripts>
</configuration>
</plugin>
plugins {
id 'groovy' // compiles src/main/groovy; build-time logic goes straight in build.gradle
}
Wiring it together
With the plugins above bound to their phases, mvn verify runs Surefire, then Failsafe, then the JaCoCo
check, then the Checkstyle/PMD/SpotBugs check goals — one command that passes only if tests, coverage, and
static analysis all pass. The Gradle equivalent is gradle check, which the test task and each analysis
plugin hook into (the custom integration-test suite only when wired as shown above). That is the command a CI
pipeline should gate merges on. The SonarQube scan (sonar:sonar / sonar) and the Maven Central deploy run
after that gate, as separate CI steps — typically only on the main branch and on tags respectively.
Generating API Docs with javadoc
javadoc turns doc comments
(/** … */ immediately before a declaration) into linked HTML. Block tags document the contract;
inline tags format text. See
the Javadoc tutorial section.
For the doc-comment format itself — block and inline tags, package/module docs, documentation inheritance, and worked examples — see Javadoc. Run the tool directly, or let the build call it:
javadoc -d apidocs -sourcepath src -subpackages com.example \
-link https://docs.oracle.com/en/java/javase/25/docs/api/
mvn javadoc:javadoc # output under target/site/apidocs
./gradlew javadoc # output under build/docs/javadoc
Both mvn test and ./gradlew test run the JUnit suite through the build’s test task; writing those
tests is covered in Testing.
See Also
-
Getting Started — installing a JDK and the first compile-and-run cycle.
-
Packages and Modules — the classpath and module path that
javac,jdeps, andjlinkoperate on. -
Testing — the
junit-jupiterdependency and the suitesmvn testandgradle testexecute. -
Annotations and Reflection — annotation processors that run as part of
javac. -
Maven Quality Plugins — the same coverage and static-analysis plugins configured for a Spring Boot Maven build, with multi-module aggregation.