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).