1. java
  2. /build tools

Master Java Build Tools - Maven and Gradle

Java Build Tools

Build tools are essential components of modern Java development that automate the compilation, testing, packaging, and deployment of applications. They manage dependencies, execute tasks, and ensure consistent builds across different environments.

Table of Contents

  1. Build Tool Fundamentals
  2. Popular Java Build Tools
  3. Build Lifecycle and Phases
  4. Dependency Management
  5. Best Practices

Build Tool Fundamentals

Build tools solve several critical problems in Java development:

Why Build Tools Matter

  • Automation: Eliminate manual compilation and packaging steps
  • Consistency: Ensure builds work the same way across environments
  • Dependency Management: Handle external libraries and their versions
  • Testing Integration: Run tests as part of the build process
  • Deployment: Package and deploy applications efficiently

Core Build Tool Concepts

// Example project structure that build tools manage
my-java-project/
├── src/
│   ├── main/
│   │   ├── java/          // Source code
│   │   └── resources/     // Configuration files
│   └── test/
│       ├── java/          // Test code
│       └── resources/     // Test resources
├── target/                // Build output (Maven)
├── build/                 // Build output (Gradle)
├── pom.xml               // Maven configuration
├── build.gradle          // Gradle configuration
└── README.md

Apache Maven

Maven uses a declarative approach with XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <junit.version>5.9.2</junit.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Gradle

Gradle uses a more flexible, programmatic approach:

plugins {
    id 'java'
    id 'application'
}

group = 'com.example'
version = '1.0.0'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.0'
}

application {
    mainClass = 'com.example.Application'
}

test {
    useJUnitPlatform()
}

Build Lifecycle and Phases

Maven Build Lifecycle

Maven follows a predefined lifecycle with specific phases:

# Common Maven commands
mvn clean           # Clean previous builds
mvn compile         # Compile source code
mvn test           # Run unit tests
mvn package        # Create JAR/WAR file
mvn install        # Install to local repository
mvn deploy         # Deploy to remote repository

# Combined phases
mvn clean install  # Clean and build
mvn clean package  # Clean and package

Gradle Build Lifecycle

Gradle uses a task-based approach:

# Common Gradle commands
./gradlew clean     # Clean build directory
./gradlew build     # Full build (compile, test, package)
./gradlew test      # Run tests only
./gradlew jar       # Create JAR file
./gradlew bootRun   # Run Spring Boot application

# Task dependencies
./gradlew build --info  # Show detailed build information

Dependency Management

Dependency Scopes and Configurations

Different scopes determine when dependencies are available:

<!-- Maven dependency scopes -->
<dependencies>
    <!-- Available during compilation and runtime -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>6.0.0</version>
        <scope>compile</scope> <!-- Default scope -->
    </dependency>
    
    <!-- Only available during testing -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.9.2</version>
        <scope>test</scope>
    </dependency>
    
    <!-- Provided by runtime environment -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
// Gradle dependency configurations
dependencies {
    // Available during compilation and runtime
    implementation 'org.springframework:spring-core:6.0.0'
    
    // Only for compilation (not included in runtime)
    compileOnly 'org.projectlombok:lombok:1.18.26'
    
    // Only for testing
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
    
    // Runtime only (not needed for compilation)
    runtimeOnly 'com.h2database:h2:2.1.214'
}

Version Management

<!-- Maven: Using properties for version management -->
<properties>
    <spring.version>6.0.0</spring.version>
    <junit.version>5.9.2</junit.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>
// Gradle: Using version catalogs (modern approach)
// gradle/libs.versions.toml
[versions]
spring = "6.0.0"
junit = "5.9.2"

[libraries]
spring-core = { module = "org.springframework:spring-core", version.ref = "spring" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }

// build.gradle
dependencies {
    implementation libs.spring.core
    testImplementation libs.junit.jupiter
}

Best Practices

Project Structure

Follow standard conventions:

src/
├── main/
│   ├── java/
│   │   └── com/example/myapp/
│   │       ├── Application.java
│   │       ├── controller/
│   │       ├── service/
│   │       └── repository/
│   └── resources/
│       ├── application.properties
│       └── static/
└── test/
    ├── java/
    │   └── com/example/myapp/
    │       ├── controller/
    │       ├── service/
    │       └── repository/
    └── resources/
        └── application-test.properties

Build Configuration Best Practices

  1. Use Properties/Variables: Centralize version management
  2. Minimize Dependencies: Only include what you actually use
  3. Separate Test Dependencies: Keep test and production dependencies separate
  4. Use Dependency Management: Leverage BOMs (Bill of Materials) for consistent versions
  5. Enable Reproducible Builds: Lock dependency versions

Multi-Module Projects

<!-- Parent POM for multi-module Maven project -->
<project>
    <groupId>com.example</groupId>
    <artifactId>my-parent</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>my-core</module>
        <module>my-web</module>
        <module>my-data</module>
    </modules>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.1.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
// Gradle multi-project build
// settings.gradle
rootProject.name = 'my-parent'
include 'my-core', 'my-web', 'my-data'

// build.gradle (root)
subprojects {
    apply plugin: 'java'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
    }
}

Section Overview

This section covers comprehensive Java build tool usage:

Maven

Learn Apache Maven fundamentals, POM configuration, lifecycle phases, and plugin usage.

Gradle

Master Gradle build scripts, task management, dependency configurations, and advanced features.

Dependency Management

Understand dependency resolution, version conflicts, and best practices for managing external libraries.

Build Lifecycle

Explore build phases, task execution, automation, and CI/CD integration.

Performance and Optimization

Build Performance Tips

# Maven performance optimizations
mvn clean install -T 4          # Parallel builds (4 threads)
mvn clean install -o            # Offline mode (skip dependency checks)
mvn clean install -DskipTests   # Skip test execution

# Gradle performance optimizations
./gradlew build --parallel       # Enable parallel execution
./gradlew build --build-cache    # Use build cache
./gradlew build --daemon         # Use Gradle daemon

Dependency Optimization

// Gradle: Exclude transitive dependencies
dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    implementation 'org.springframework.boot:spring-boot-starter-jetty'
}

// Force specific versions to resolve conflicts
configurations.all {
    resolutionStrategy {
        force 'org.slf4j:slf4j-api:2.0.7'
    }
}

Key Takeaways

  • Build tools automate and standardize the development workflow
  • Maven uses convention over configuration with XML-based declarative approach
  • Gradle provides more flexibility with programmatic build scripts
  • Proper dependency management prevents version conflicts and reduces build size
  • Multi-module projects enable better code organization and reusability
  • Performance optimization techniques can significantly reduce build times

Modern Java development relies heavily on build tools to manage complexity and ensure consistent, reproducible builds across different environments and team members.