Build Tools — Maven vs Gradle in 2026

Every Java project has a build tool. In 2026, there are still two credible options, and the choice is more political than technical. Maven is verbose but universal. Gradle is concise but complex. This file gives you the honest verdict, sets up both, and shows you the anatomy of each config file.

The 2026 Verdict

  • Default for enterprise Spring Boot / server-side Java: Maven. Predictable, XML-only, no Groovy/Kotlin DSL surprises.

  • Default for Android + polyglot + complex build logic: Gradle. Kotlin DSL is now stable; Groovy DSL is being phased out.

  • What Zoho likely uses: Maven. Almost all enterprise Java shops in India stick with Maven for backend services.

  • What OSS Java projects use: ~65% Maven / 35% Gradle in 2026 (rough estimate from GitHub topic search).

Learn Maven first. Add Gradle later if a project needs it.

The Comparison

Dimension

Maven

Gradle

Config format

pom.xml (XML)

build.gradle.kts (Kotlin DSL) or build.gradle (Groovy, legacy)

Config lines

100-500 for typical Spring Boot

20-100 (concise)

Learning curve

Low — declarative, no logic

Medium-high — full programming language

Build speed

Slower baseline; parallel + -T 1C helps

Faster with build cache + daemon

Incremental builds

Basic (Maven Reactor)

Excellent (task-level incremental)

Dependency resolution

Predictable, closest-wins

Rule-based, sometimes surprising

Plugin ecosystem

Massive (Maven Central plugins)

Massive (Gradle plugin portal)

IDE support

Best in class (all IDEs)

Best in class (all IDEs)

CI/CD friendliness

Trivial

Trivial

Debuggability

Easy — read XML top-to-bottom

Harder — logic can be anywhere

Enterprise adoption

Overwhelming

Growing but slower

Maven — What You Actually Use

Anatomy of a Spring Boot pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <!-- Parent: inherits Spring Boot defaults (dep management, plugins) -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0</version>
        <relativePath/>
    </parent>

    <groupId>com.raghul.example</groupId>
    <artifactId>petstore-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <version>1.20.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

The Maven commands you use daily

./mvnw clean install         # Clean + build + test + install to local ~/.m2
./mvnw clean package          # Clean + build + test + create JAR/WAR
./mvnw test                   # Run tests only
./mvnw -Dtest=UserServiceTest test    # Run one test class
./mvnw -Dtest=UserServiceTest#shouldSave test   # Run one test method
./mvnw dependency:tree        # Show transitive deps
./mvnw dependency:analyze     # Find unused / undeclared deps
./mvnw versions:display-dependency-updates    # Check for newer versions
./mvnw versions:display-plugin-updates
./mvnw spring-boot:run        # Run Spring Boot app (dev)
./mvnw -T 1C clean install    # Parallel build, 1 thread per core
./mvnw -DskipTests package    # Skip tests (CI/emergency only)

~/.m2/settings.xml — the personal Maven config

Location: ~/.m2/settings.xml. Common uses:

  • Corporate proxy config for Zoho’s network

  • Alternate Maven Central mirror (JFrog / Nexus)

  • Server credentials for private repos

  • Custom local repository path

<settings>
    <mirrors>
        <mirror>
            <id>zoho-nexus</id>
            <mirrorOf>*</mirrorOf>
            <url>https://nexus.internal.zoho/repository/maven-public/</url>
        </mirror>
    </mirrors>
    <servers>
        <server>
            <id>zoho-nexus</id>
            <username>${env.NEXUS_USER}</username>
            <password>${env.NEXUS_PASS}</password>
        </server>
    </servers>
</settings>

Note: Zoho’s actual Nexus URL will differ; ask your platform team.

Maven Wrapper (mvnw) — always commit this

Every new project should ship a Maven Wrapper. It pins the Maven version per repo so no one has to install Maven globally:

mvn -N wrapper:wrapper -Dmaven=3.9.9
git add mvnw mvnw.cmd .mvn

Commit .mvn/wrapper/maven-wrapper.properties and .mvn/wrapper/MavenWrapperDownloader.java. Never commit ~/.m2/repository.

Gradle — When You Have to Use It

Anatomy of a Spring Boot build.gradle.kts

plugins {
    java
    id("org.springframework.boot") version "3.4.0"
    id("io.spring.dependency-management") version "1.1.6"
}

group = "com.raghul.example"
version = "0.0.1-SNAPSHOT"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.testcontainers:postgresql:1.20.4")
}

tasks.test {
    useJUnitPlatform()
}

The Gradle commands you use daily

./gradlew build          # Clean + compile + test + assemble
./gradlew test           # Test only
./gradlew bootRun        # Run Spring Boot app
./gradlew dependencies   # Dep tree
./gradlew --refresh-dependencies build   # Force redownload
./gradlew clean          # Clean build/ dir
./gradlew --scan build   # Detailed HTML build report (uploads to Gradle Enterprise)
./gradlew bootBuildImage # Build a Docker image via Paketo buildpacks

Gradle Wrapper — always commit this

gradle wrapper --gradle-version 8.11
git add gradlew gradlew.bat gradle/

The Real Differences That Matter

Thing

Maven

Gradle

First ./mvnw/./gradlew build in fresh clone

30s-2min

20s-1min (with daemon)

Incremental rebuild after 1 file change

5-10s

1-3s

Reading someone else’s pom.xml cold

Easy

Sometimes hard

Complex conditional logic

Painful (profiles)

Native (it’s Kotlin)

Company policy compliance in India MNCs

Universally supported

Often supported

Community help (Stack Overflow)

Massive

Massive

What NOT to Do

  • Do NOT install Maven or Gradle globally. Use wrappers (mvnw / gradlew) in every project. Version drift is the #1 build support ticket.

  • Do NOT mix Maven and Gradle in the same project. Pick one.

  • Do NOT commit target/ or build/. Both are output directories.

  • Do NOT commit ~/.m2/. That’s your local cache.

  • Do NOT skip tests in CI. -DskipTests is for local iteration only.

  • Do NOT use Ant. It’s 2026. Ant is a historical artifact.

The Plan for This 13-Month Roadmap

Month

Focus

M1

Install both via SDKMAN. Create one hello-world project with Maven.

M2-M6

Use Maven exclusively. Read pom.xml files critically. Learn dependency:tree.

M7

Spring Boot with Maven. Learn Maven profiles for dev/test/prod.

M8

Try Gradle for one microservice in your portfolio to feel the difference.

M9-M11

Use whichever fits the project. Default Maven.

M12

study prep: be able to explain Maven lifecycle phases (validate, compile, test, package, verify, install, deploy) and Gradle task graph.

Publishing to Internal Nexus / Artifactory (Zoho scenario)

Every mid-large Java org runs an internal Nexus (Sonatype) or Artifactory (JFrog). To publish:

Maven:

<distributionManagement>
    <repository>
        <id>zoho-nexus-releases</id>
        <url>https://nexus.internal.zoho/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>zoho-nexus-snapshots</id>
        <url>https://nexus.internal.zoho/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

Then ./mvnw deploy. Credentials in ~/.m2/settings.xml.

Ask your platform team for the actual URL and credentials pattern. Don’t hard-code passwords.

Return to README.md · Next: 04_git_and_workflow.md