Maven & Gradle

No serious Java project is built by hand. By the end of this lesson you'll write a pom.xml and a build.gradle , add pinned dependencies, run the Maven lifecycle, and know exactly when to reach for Maven versus Gradle.

Learn Maven & Gradle in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You should be comfortable with Unit Testing (build tools run your tests) and basic OOP project structure. A little XML familiarity helps for Maven, but it is not required.

A build tool turns your source code into a runnable, shippable artifact. It fetches the libraries you need, compiles your code, runs your tests, and packages the result into a JAR — every time, the same way, on every machine.

💡 Analogy: A build tool is like a recipe with an automatic grocery service. The recipe (your pom.xml or build.gradle ) lists the ingredients (dependencies) and the steps (compile, test, package). The tool does the shopping for you — it downloads each ingredient and the ingredients those ingredients need (transitive dependencies) — then cooks the dish in the right order. Maven hands you a fixed recipe template; Gradle lets you write your own.

Before build tools, developers downloaded JARs by hand and fought "it works on my machine" bugs. Maven (2004) and Gradle (2012) ended that: declare what you need, and the tool produces an identical build for everyone.

Maven is built on convention over configuration : put your code in src/main/java and your tests in src/test/java , and Maven just knows what to do. You drive it through lifecycle phases that always run in order.

The key rule: each phase runs every phase before it . So mvn package automatically validates, compiles, and tests first. Read the worked output below — you can see all four phases run from one command.

The pom.xml (Project Object Model) is Maven's recipe. Three things make it work:

Fill in the artifactId, version, and the dependency scope. The expected result is in the comments.

Gradle does everything Maven does, but the recipe is a real script ( build.gradle in Groovy, or build.gradle.kts in Kotlin) instead of XML. The payoff is brevity and speed.

plugins {' ... '} — turn on capabilities (the java plugin adds compile/test/jar tasks).

repositories {' mavenCentral() '} — where dependencies are downloaded from.

implementation '...' — a compile dependency, written on one line (Maven's compile scope).

testImplementation '...' — a test-only dependency (Maven's test scope).

tasks — the unit of work Gradle runs ( build , test , run , or your own).

A single implementation line replaces a seven-line XML block. Gradle is also typically 2-10x faster on repeat builds because it caches task outputs and keeps a daemon warm.

Add the repository and the two dependency configurations. The expected result is in the comments.

You rarely depend on just one library. When you add jackson-databind , it needs jackson-core and jackson-annotations — so the build downloads those too. Those are transitive dependencies : dependencies of your dependencies.

Sometimes two libraries pull in different versions of the same transitive dependency — a version conflict . Maven resolves it by "nearest wins" (the version closest to your project in the tree). To see and debug the full graph, use mvn dependency:tree or ./gradlew dependencies , then pin the version you want explicitly to force a single, predictable answer.

No blanks this time — just the brief. Write the dependency lines from scratch, then verify with the dependency report.

You can now write a pom.xml with GAV coordinates and scoped dependencies, run the Maven lifecycle ( validate → compile → test → package → install ), write a build.gradle with dependencies and tasks, read a transitive dependency tree, fix version conflicts, and choose Maven or Gradle for the job. That is the build-tool literacy every Java team expects.

Next up: Modular Java — the Java Platform Module System (JPMS).

Practice quiz

What are the three GAV coordinates that uniquely identify an artifact?

  • name, type, scope
  • repo, branch, tag
  • group, artifact, version
  • id, key, value

Answer: group, artifact, version. groupId (who), artifactId (what), and version (which release) form coordinates like com.mycompany:my-app:1.0.0.

What is the main difference between Maven and Gradle config files?

  • Maven uses XML (pom.xml), Gradle uses a script (build.gradle)
  • Maven uses code, Gradle uses XML
  • Both use YAML
  • Both use JSON

Answer: Maven uses XML (pom.xml), Gradle uses a script (build.gradle). Maven configures in XML (pom.xml) with strict conventions; Gradle uses a Groovy/Kotlin script, more concise and flexible.

Because each Maven phase runs every phase before it, what does 'mvn package' do first?

  • Nothing else
  • Only compile
  • install and deploy
  • validate, compile, then test

Answer: validate, compile, then test. Lifecycle phases run in order, so package automatically validates, compiles, and tests before packaging.

What is a transitive dependency?

  • A dependency you list twice
  • A dependency of your dependency that you never listed yourself
  • A test-only dependency
  • A dependency on the JDK

Answer: A dependency of your dependency that you never listed yourself. jackson-databind pulls in jackson-core and jackson-annotations automatically — those are transitive dependencies.

Which Maven scope puts a dependency only on the test classpath?

  • test
  • compile
  • runtime
  • provided

Answer: test. The test scope keeps a dependency (like JUnit) off the main classpath and out of the shipped JAR.

What is Gradle's equivalent of Maven's compile scope?

  • testImplementation
  • runtimeOnly
  • implementation
  • compileOnly

Answer: implementation. implementation '...' is a one-line compile dependency, replacing Maven's seven-line XML block.

Why should you always commit and use the wrapper (./gradlew or mvnw)?

  • It builds faster
  • It pins the exact build-tool version so everyone builds identically
  • It is required by Java 21
  • It skips tests

Answer: It pins the exact build-tool version so everyone builds identically. The wrapper pins the tool version for the whole team, avoiding 'works on my machine' build differences.

What does a -SNAPSHOT version mean?

  • An immutable release
  • A backup copy
  • A security patch
  • An in-progress build that can change at any time and is re-downloaded

Answer: An in-progress build that can change at any time and is re-downloaded. A -SNAPSHOT is a mutable, in-progress build; a release version like 1.2.0 is immutable for reproducible builds.

Why should you pin an exact dependency version like 2.17.0?

  • It downloads faster
  • So today's build is identical to next year's — LATEST or ranges can silently break it
  • It uses less disk
  • Maven requires it

Answer: So today's build is identical to next year's — LATEST or ranges can silently break it. Pinning an exact version makes builds reproducible; LATEST or open ranges let a new release break your build.

Which command prints the resolved transitive dependency tree in Gradle?

  • ./gradlew build
  • ./gradlew clean
  • ./gradlew dependencies
  • ./gradlew run

Answer: ./gradlew dependencies. ./gradlew dependencies prints the resolved graph (Maven's equivalent is mvn dependency:tree).