Maven and Gradle are the two dominant build tools for JVM projects. Maven ruled the Java world for over a decade with its strict conventions. Gradle came later and challenged everything — Groovy/Kotlin DSL over XML, incremental builds, and Android as a first-class target. This guide tells you exactly when each wins.
At a glance
| Maven | Gradle | |
|---|---|---|
| Released | 2004 (Apache) | 2012 |
| Config language | XML (pom.xml) |
Groovy DSL or Kotlin DSL (build.gradle / build.gradle.kts) |
| Build model | Fixed lifecycle (phases) | Task graph (DAG) |
| Incremental builds | Limited | Yes — only re-runs changed tasks |
| Build cache | No (by default) | Yes — local + remote cache |
| Parallel execution | Optional (--threads) |
Yes (default for multi-project) |
| Android support | Not supported | Official build tool for Android |
| Kotlin support | Good (Kotlin Maven Plugin) | Excellent (Kotlin DSL native) |
| Learning curve | Gentle — conventions over config | Steeper — flexible but more to learn |
| IDE support | Excellent (IntelliJ, Eclipse, NetBeans) | Excellent (IntelliJ, Android Studio) |
| Plugin ecosystem | Mature (~10,000+ plugins on Central) | Growing fast, fewer but high quality |
| Community | Huge, long-established | Large, growing — dominant in Android |
How Maven works
Maven uses a fixed build lifecycle with phases like validate → compile → test → package → verify → install → deploy. Every plugin binds to a phase; you run a phase and Maven executes everything bound up to and including it.
pom.xml structure
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Common Maven commands
mvn compile # Compile source
mvn test # Run tests
mvn package # Build JAR/WAR
mvn install # Install to local ~/.m2 repo
mvn deploy # Push to remote repo
mvn clean package # Clean then build
mvn spring-boot:run # Run Spring Boot app
mvn dependency:tree # Print dependency tree
mvn versions:display-dependency-updates # Show outdated deps
Maven dependency scopes
| Scope | Available at | Packaged | Example |
|---|---|---|---|
compile (default) |
Compile + runtime + test | Yes | spring-core |
test |
Test compile + runtime only | No | junit |
provided |
Compile + test | No | jakarta.servlet-api |
runtime |
Runtime + test | Yes | JDBC drivers |
system |
Compile only | No | Local JAR |
import |
POM scope only (BOM) | — | spring-boot-dependencies |
How Gradle works
Gradle uses a directed acyclic graph (DAG) of tasks. Instead of fixed phases, you define tasks and their dependencies. Gradle runs only what's needed, checks if outputs are up-to-date, and caches results.
build.gradle.kts (Kotlin DSL)
plugins {
id("org.springframework.boot") version "3.3.0"
id("io.spring.dependency-management") version "1.1.5"
kotlin("jvm") version "2.0.0"
kotlin("plugin.spring") version "2.0.0"
}
group = "com.example"
version = "1.0.0"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.withType<Test> {
useJUnitPlatform()
}
build.gradle (Groovy DSL — still common)
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
Common Gradle commands
./gradlew build # Compile, test, assemble
./gradlew test # Run tests only
./gradlew bootRun # Run Spring Boot app
./gradlew dependencies # Print dependency tree
./gradlew tasks # List all available tasks
./gradlew clean build # Clean then build
./gradlew build --parallel # Parallel build
./gradlew build --build-cache # Use build cache
./gradlew build --scan # Publish build scan to scans.gradle.com
Performance comparison
Build speed is the most visible difference. Gradle wins decisively for large multi-module projects.
| Scenario | Maven | Gradle |
|---|---|---|
| Small project (cold) | ~5–10s | ~5–10s (similar) |
| Large project (cold) | 3–10 min | 1–4 min |
| Incremental (one file changed) | Full rebuild | Only affected tasks |
| No changes (up-to-date check) | Seconds (skips tests only) | < 1s (up-to-date) |
| Multi-module parallel | Limited | Yes, significant speedup |
| Remote build cache hit | Not supported | Near-instant (cache hit) |
Why Gradle is faster
- Incremental compilation — only re-compiles changed classes and their dependents
- Up-to-date checks — compares input/output checksums; skips task if nothing changed
- Build cache — stores task outputs locally and remotely; reuses across machines (CI hits dev cache)
- Parallel task execution — independent tasks run concurrently
- Configuration cache — skips configuration phase on repeated builds (Gradle 8+)
Dependency management
Both tools use Maven Central and can consume any Maven/Ivy artifact. The key differences are in how they handle conflicts and BOMs.
Version conflict resolution
Maven uses nearest-wins — the dependency closest to the root in the tree wins. This is predictable but can include old transitive dependencies.
Gradle uses highest-version-wins by default — if multiple versions of the same dependency appear, the highest version is selected. Override with:
// Gradle: force a specific version
configurations.all {
resolutionStrategy {
force("com.fasterxml.jackson.core:jackson-databind:2.17.0")
}
}
<!-- Maven: use dependencyManagement to enforce version -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>
</dependencyManagement>
BOM (Bill of Materials) support
Both tools support importing a BOM to align dependency versions:
// Gradle
dependencies {
implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.0"))
implementation("org.springframework.boot:spring-boot-starter-web") // no version needed
}
<!-- Maven -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Multi-project builds
Both tools support multi-module projects, but Gradle scales better.
Maven multi-module
my-app/
├── pom.xml ← parent POM (packaging=pom)
├── api/
│ └── pom.xml
├── service/
│ └── pom.xml
└── web/
└── pom.xml
<!-- parent pom.xml -->
<modules>
<module>api</module>
<module>service</module>
<module>web</module>
</modules>
Maven builds modules in declared order. Parallel builds (-T 4 or -T 1C) help but are limited.
Gradle multi-project
my-app/
├── settings.gradle.kts ← declares all subprojects
├── build.gradle.kts ← root conventions
├── api/
│ └── build.gradle.kts
├── service/
│ └── build.gradle.kts
└── web/
└── build.gradle.kts
// settings.gradle.kts
rootProject.name = "my-app"
include("api", "service", "web")
Gradle analyzes the task DAG and automatically parallelizes independent subprojects. With build cache, unchanged modules are never rebuilt.
Plugin ecosystem comparison
| Category | Maven | Gradle |
|---|---|---|
| Plugin count | ~10,000+ (Maven Central) | Thousands (plugins.gradle.org) |
| Spring Boot | spring-boot-maven-plugin ✓ |
org.springframework.boot ✓ |
| Kotlin | kotlin-maven-plugin ✓ |
kotlin("jvm") — first-class ✓ |
| Android | ✗ Not supported | ✓ Official AGP (Android Gradle Plugin) |
| Code coverage | JaCoCo Maven Plugin | JaCoCo Gradle Plugin |
| Static analysis | Checkstyle, SpotBugs, PMD | Same + Detekt (Kotlin) |
| Native/GraalVM | native-maven-plugin |
org.graalvm.buildtools.native |
| Docker | Jib (Google), Spotify | Jib, Palantir, Spring Boot buildpacks |
| Custom tasks | Write a plugin (Java/Mojos) | Write a task (Kotlin/Groovy/Java inline) |
Android: Gradle only
If you're building Android apps, there is no choice — Android Studio and the Android Gradle Plugin (AGP) are the only official path. Maven is not supported.
// Android build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.myapp"
compileSdk = 35
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.compose.ui:ui:1.6.7")
testImplementation("junit:junit:4.13.2")
}
Groovy DSL vs Kotlin DSL
Gradle supports two DSLs. Kotlin DSL is now recommended for new projects:
Groovy DSL (build.gradle) |
Kotlin DSL (build.gradle.kts) |
|
|---|---|---|
| IDE autocomplete | Limited | Full type-safe autocomplete |
| Compile-time errors | No | Yes |
| Syntax | Dynamic, concise | Verbose but explicit |
| Performance | Slightly faster config | Similar in Gradle 8+ |
| Default in new projects | Older projects | Yes (since Gradle 7+) |
When to use Maven
| Situation | Reason |
|---|---|
| Standard Java/Spring Boot backend | Maven conventions match perfectly; spring-boot-maven-plugin is mature |
| Enterprise team with Maven expertise | Lower learning curve; familiar XML; lots of tutorials |
| Strict reproducibility required | Fixed lifecycle; less "magic" configuration |
| CI/CD pipelines already Maven-based | No migration cost |
| Library publishing to Maven Central | Maven's release lifecycle and nexus-staging-maven-plugin are well-documented |
| Simple, small project | Convention over configuration; just works for standard layouts |
When to use Gradle
| Situation | Reason |
|---|---|
| Android development | AGP is mandatory |
| Kotlin project | Kotlin DSL gives type-safe build scripts |
| Large multi-module monorepo | Incremental builds + build cache + parallel execution = massive speedup |
| Custom build logic | Task graph is more flexible than lifecycle phases |
| Polyglot build | Can build Java, Kotlin, Groovy, Scala, C/C++, Python in one build |
| CI speed is critical | Remote build cache can cut CI time by 50–90% in large repos |
| Modern Kotlin/Spring project | Kotlin DSL + Spring Boot Gradle Plugin is the community default |
Migration: Maven to Gradle
Gradle includes a built-in migration tool:
# In a Maven project directory
gradle init --type java-application
# or for existing Maven project:
gradle init
# Gradle reads pom.xml and generates build.gradle.kts automatically
The generated build script handles most dependencies and plugins. Manual review needed for:
- Custom Maven plugins (need Gradle equivalents)
- Complex
executionblocks in Maven plugins - Profile-based builds (use Gradle variants/flavors instead)
Maven vs Gradle vs other build tools
| Tool | Language | JVM | Android | Speed | When to use |
|---|---|---|---|---|---|
| Maven | XML | ✓ | ✗ | Medium | Standard Java/Spring projects |
| Gradle | Kotlin/Groovy | ✓ | ✓ | Fast | Android, Kotlin, large monorepos |
| Ant | XML | ✓ | ✗ | Medium | Legacy projects |
| Bazel | Starlark | ✓ | ✓ | Very fast | Google-scale monorepos |
| sbt | Scala | ✓ | ✗ | Medium | Scala-first projects |
| Mill | Scala | ✓ | ✗ | Fast | Alternative to sbt |
| Make | Makefile | — | — | Fast | Non-JVM or wrapper scripts |
Common mistakes
| Mistake | What goes wrong | Fix |
|---|---|---|
Using compile scope in Maven |
Deprecated in Maven 3; silently treated as compile but generates warnings |
Use implementation in Gradle or compile→remove in Maven 3 (compile scope still works but deprecated) |
Gradle: mutable configurations.all {} without resolutionStrategy |
Version conflicts not resolved | Add explicit resolutionStrategy.force(...) |
Running mvn package without clean |
Stale classes in target/ cause weird failures | Use mvn clean package or ./gradlew clean build |
| Gradle: using Groovy DSL in a Kotlin project | No type safety, harder to refactor | Use build.gradle.kts (Kotlin DSL) |
| Maven: managing transitive versions ad-hoc | Dependency hell | Use <dependencyManagement> or import a BOM |
Gradle: checking in .gradle/ wrapper files |
Unnecessary bloat; cache files differ per machine | Add .gradle/ to .gitignore, keep only gradle/wrapper/ |
Not using Gradle wrapper (gradlew) |
Team uses different Gradle versions, builds differ | Always commit gradlew and gradle/wrapper/gradle-wrapper.properties |
Maven: ignoring <optional>true</optional> |
Optional deps leak into consumers | Mark test-only or internal deps as optional |
Maven vs Gradle: related terms
| Term | What it means |
|---|---|
| POM | Project Object Model — Maven's XML config (pom.xml) |
| GAV | GroupId:ArtifactId:Version — Maven coordinate (e.g., org.junit.jupiter:junit-jupiter:5.10.2) |
| Lifecycle | Maven's fixed build phases (compile, test, package, install, deploy) |
| Task | Gradle's unit of work (equivalent to Maven plugin goal) |
| Build scan | Gradle's diagnostic report published to scans.gradle.com |
| Wrapper | gradlew / mvnw scripts that download the correct tool version automatically |
| BOM | Bill of Materials — POM that declares aligned dependency versions |
| Configuration cache | Gradle 8+ feature that caches the configuration phase (skips on repeated builds) |
| Maven Central | Primary artifact repository for JVM ecosystem (repo.maven.apache.org) |
| Gradle Plugin Portal | Gradle's plugin directory (plugins.gradle.org) |
FAQ
Should I use Maven or Gradle for a new Spring Boot project?
Both work equally well for Spring Boot. Maven is the default in Spring Initializr and has a smaller learning curve. Gradle is preferred if you use Kotlin, need faster CI builds, or are building a multi-module monorepo. Start with Maven if your team is already familiar with it; migrate to Gradle when build times become a problem.
Is Gradle always faster than Maven?
For small single-module projects, the difference is negligible. Gradle's advantages compound on large multi-module projects where incremental builds and build caching eliminate redundant work. On a 100+ module project, Gradle can be 5–10× faster with a warm cache.
Can I use Maven and Gradle in the same project?
Not simultaneously, but you can have both pom.xml and build.gradle.kts in a repository during migration. Many teams run Maven for production builds and use gradle init to test migrated scripts in parallel before cutting over.
Which build tool does IntelliJ IDEA support better?
IntelliJ supports both excellently. For Gradle, the Kotlin DSL gives full type-safe autocomplete — better than Groovy DSL. For Maven, IntelliJ's Maven tool window is very mature. Both tools have first-class IntelliJ integration.
Should Android developers ever use Maven?
No. Android development requires the Android Gradle Plugin (AGP). Maven cannot build Android apps. If you're targeting Android, use Gradle — it's non-negotiable.
What's the difference between gradle build and gradle assemble?
assemble compiles and packages (JAR/APK) but skips tests. build runs everything including tests and checks. Use assemble for quick package builds; build for full verification.