Build tools: Maven & Gradle
Project layout, pom.xml and build.gradle.kts, dependencies, lifecycle, running tests and packaging.
So far we compiled with javac, ran with java, and downloaded JARs by hand. That falls apart quickly: a real project has dozens of dependencies (which have their own dependencies), tests to run, and artifacts to package for deployment. Build tools automate all of it. The Java world has two big ones: Maven and Gradle. Every professional Java developer uses at least one daily.
What a build tool does#
- Dependency management: declare
gson:2.14.0, and the tool downloads it, plus everything it needs (transitive dependencies), from Maven Central, then caches it locally. - A standard project layout, so any developer (and any IDE) understands your project instantly.
- Compiling, testing and packaging with one command.
- Reproducible builds on every machine and in CI (GitHub Actions, Jenkins...).
- Plugins for everything else: code coverage, formatting, Docker images, Spring Boot.
The standard layout#
Both Maven and Gradle use the same conventional structure:
Build output goes to target/ (Maven) or build/ (Gradle). Never commit those folders; add them to .gitignore.
Installing#
For Gradle you rarely install anything globally: projects include the Gradle Wrapper (./gradlew), and IntelliJ IDEA can create either kind of project for you. SDKMAN! (sdk install gradle) is a convenient way to get the latest Gradle on Linux and macOS.
Our example project#
A tiny app that calculates tips and prints JSON using Google's Gson library:
BigDecimal is used because this is money, and the test checks two behaviours. Tests are covered properly in the next lesson.
Maven#
Maven describes a project with an XML file, the POM (Project Object Model):
What the parts mean:
- Coordinates
groupId:artifactId:versionidentify your project, exactly ascom.google.code.gson:gson:2.14.0identifies Gson. A version ending in-SNAPSHOTmeans "work in progress". maven.compiler.release= 21 compiles for Java 21.<dependencies>: libraries your code needs. The BOM import in<dependencyManagement>pins versions for a family of artifacts (all JUnit modules), so thejunit-jupiterdependency needs no version of its own.<build><plugins>: pinning plugin versions makes builds reproducible. The JAR plugin writesMain-Classinto the manifest.
Dependency scopes
The build lifecycle
Maven has fixed phases; running one runs all the phases before it:
Running mvn test prints a test summary:
If any test fails, the build fails, and nothing broken gets packaged. That's exactly what you want in CI.
Running the application
Watch out: java -jar target/tip-calculator-1.0.0.jar fails with NoClassDefFoundError: com/google/gson/Gson. A plain JAR contains only your classes, not your dependencies. Options:
Or build a single "fat" JAR containing everything, with the maven-shade-plugin (or Gradle's Shadow plugin). Spring Boot's build plugins do this for you automatically.
Inspecting dependencies
You declared two dependencies and got ten: those are transitive dependencies. When two libraries need different versions of the same artifact, Maven picks the one nearest to your project in this tree. dependency:tree is the first tool to reach for when you see NoSuchMethodError or version conflicts.
Find libraries and their latest versions on Maven Central. Keep them up to date for security fixes.
Gradle#
Gradle builds are written as code, usually in the Kotlin DSL (build.gradle.kts; older projects use Groovy build.gradle). The same project:
- The
applicationplugin adds Java support plusrunand distribution tasks. - A toolchain tells Gradle to compile and test with Java 21, downloading it if necessary.
- Dependencies use the same Maven coordinates, from the same Maven Central.
Common Gradle tasks
./gradlew build also creates build/distributions/tip-calculator-1.0.0.zip, containing your JAR, all dependency JARs and start scripts. Unzip it anywhere and run bin/tip-calculator. On Windows, use gradlew.bat.
Gradle is fast thanks to incremental builds (only redo what changed), a build cache, and a background daemon.
Maven or Gradle?#
Both are excellent, and both use Maven Central. Learn to read both. Your team or framework usually decides; for example, Spring Initializr offers either.
Multi-module projects (briefly)#
Large applications are often split into modules (shop-domain, shop-api, shop-web). Maven uses a parent POM with <modules>; Gradle uses include("shop-domain", "shop-api") in settings.gradle.kts. Each module has its own build file and can depend on the others.
Common mistakes#
- Putting code in the wrong folder (
src/javainstead ofsrc/main/java), so nothing compiles or tests are not found. - Forgetting
useJUnitPlatform()in Gradle, or using an ancient Surefire version in Maven, so tests silently don't run (look for "Tests run: 0"). - Expecting
java -jaron a plain JAR to include dependencies. - Committing
target/orbuild/, or not committing the Gradle Wrapper. - Unpinned or outdated dependency versions.
- Using
compile/implementationscope for test libraries.
What's next#
Our build already ran two tests. Next, we learn to write good ones: unit testing with JUnit 5.
Check your understanding
Quick quiz
1.In Maven, which dependency scope makes a library available only when compiling and running tests?
2.What does
mvn packagedo?3.Why commit the Gradle Wrapper (
gradlew,gradle/wrapper/) to version control?
Finished reading?
Mark this lesson complete to track your progress.