Build Tools · Lesson 1
Introduction to Build Tools
What Are Build Tools?
Build tools automate the software development workflow: compiling source code, managing dependencies, running tests, packaging applications, and deploying. Instead of manually managing library versions and compilation, you declare what you need, and the build tool handles it. Maven and Gradle are the two dominant Java build tools. Maven uses declarative XML configuration; Gradle uses Groovy/Kotlin scripts for more flexibility. Both dramatically reduce setup time and ensure reproducible builds across environments.
Maven: The Standard Java Build Tool
Maven organizes projects using conventions over configuration. If you follow Maven's standard directory structure (src/main/java, src/test/java, target), almost no configuration is needed. Maven's Project Object Model (POM) XML file declares dependencies, plugins, and build steps. Maven repositories (Maven Central) provide pre-built libraries — no need to download JARs manually.
<!-- pom.xml: Maven configuration file -->
<project>
<modelVersion>4.0.0</modelVersion>
<!-- Project identification -->
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<name>My Application</name>
<!-- Java version -->
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- Dependencies -->
<dependencies>
<!-- JUnit for testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot for web development -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<!-- MySQL database driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
<!-- Build configuration -->
<build>
<plugins>
<!-- Compiler plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<!-- Spring Boot Maven plugin (for running and packaging) -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.0</version>
</plugin>
</plugins>
</build>
</project>
Maven Commands and Lifecycle
Maven's build lifecycle has phases: validate, compile, test, package, install, deploy. The command mvn phase executes all phases up to that point. Common commands:
# Compile source code
mvn compile
# Run tests
mvn test
# Package as JAR/WAR
mvn package
# Install to local repository
mvn install
# Deploy to remote repository
mvn deploy
# Clean build artifacts
mvn clean
# Chain phases: clean, compile, test, package
mvn clean package
# Skip tests during build
mvn clean package -DskipTests
# Run a specific test class
mvn test -Dtest=MyTestClass
# Display project information
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin
Dependency Management
Maven downloads dependencies from remote repositories (Maven Central is default). Specify version ranges, exclusions, and scopes (compile, test, runtime). Maven resolves transitive dependencies automatically — if you depend on Spring, Maven automatically downloads Spring's dependencies.
<!-- pom.xml: Dependency management -->
<dependencyManagement>
<dependencies>
<!-- Define versions for consistency -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.20</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Exclude transitive dependency -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Version range (flexible versioning) -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>[1.2.0, 1.3)</version>
</dependency>
<!-- Compile scope: used during compilation and runtime -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>compile</scope>
</dependency>
<!-- Test scope: only for testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Runtime scope: not needed for compilation, only runtime -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Understanding Artifact Coordinates
Maven uses coordinates (groupId, artifactId, version) to uniquely identify libraries. groupId is the organization/domain; artifactId is the project name; version is the release number. Together they form the repository path: com/example/my-library/1.0.0/my-library-1.0.0.jar.
groupId: org.springframework
artifactId: spring-core
version: 5.3.20
Repository path: org/springframework/spring-core/5.3.20/spring-core-5.3.20.jar
Maven Central URL: https://repo.maven.apache.org/maven2/org/springframework/spring-core/5.3.20/spring-core-5.3.20.jar
| Scope | Included in | Purpose |
|---|---|---|
| compile | Compile, test, runtime | Main dependencies |
| test | Test only | Testing libraries (JUnit, Mockito) |
| runtime | Test, runtime | Not needed for compilation (databases, drivers) |
| provided | Compile, test | Supplied by container (Java EE APIs) |
| system | Compile, test | JAR from filesystem (deprecated) |
Common Mistake: Not understanding dependency scopes. If you accidentally include a test library with compile scope, it ships with your production JAR, bloating the build. Always use test scope for testing libraries. Also, dependency conflicts arise when different libraries require different versions of the same dependency. Maven resolves using "nearest in dependency tree" — understand and use
<dependencyManagement>to ensure consistent versions.
Best Practices for Maven
- Use dependency management in parent POM for consistent versions
- Keep pom.xml simple: delegate complex logic to plugins
- Use standard directory structure: Maven expects src/main/java, src/test/java
- Specify Java version explicitly (maven.compiler.source, maven.compiler.target)
- Exclude unnecessary transitive dependencies to reduce JAR size
- Use Maven repositories (Maven Central) instead of checking JARs into git
- Configure repositories if using private libraries (company Nexus/Artifactory)
- Use profiles for environment-specific builds (dev, staging, prod)
Real-World Example: Multi-Module Maven Project
A company's microservices project uses a parent POM defining common dependencies and build settings. Each microservice is a child module with its own pom.xml, inheriting from the parent. The parent specifies Spring Boot version 2.7.0, MySQL driver 8.0.33, JUnit 4.13.2 — all services use the same versions. Each service has its specific dependencies (some add Kafka, others add Redis). Running mvn clean package from the parent compiles and tests all services, creating JAR files for deployment. Maven's dependency management ensures no version conflicts across the organization.
Quiz Check