Unit 4 - Notes

INT332 8 min read

Unit 4: Maven Build Automation

1. Introduction to Build Tools

Why Build Tools Exist

In software development, building an application manually involves a sequence of repetitive tasks: downloading dependencies, compiling source code, running tests, packaging compiled code into distributable formats (like JAR or WAR), and deploying. As projects grow in size and complexity, manual builds become unsustainable. Build tools exist to automate these tasks, providing a consistent, repeatable, and less error-prone way to assemble software.

Problems Solved by Automated Builds

  • Dependency Management Hell: Automatically downloads and links required libraries (and their dependencies) rather than requiring developers to manually download and place .jar files in a lib folder.
  • Inconsistencies Across Environments: Ensures the application builds exactly the same way on a developer's local machine, a testing server, and in the production CI/CD pipeline.
  • Repetitive Manual Labor: Eliminates the need to manually execute multiple CLI commands to compile, test, and package.
  • Failed Deployments due to Broken Tests: Automated builds integrate testing; if tests fail, the build fails, preventing broken code from being packaged or deployed.
  • Complex Project Structures: Standardizes project structures so any developer can easily understand where source code, tests, and resources are located.

2. Core Maven Concepts

Project Object Model (POM)

The POM is the fundamental unit of work in Maven. It is an XML file (pom.xml) located in the root directory of a Maven project. The POM contains information about the project and configuration details used by Maven to build the project.

  • Coordinates: Identifies the project uniquely using groupId, artifactId, and version (GAV).
  • Dependencies: Lists external libraries required by the project.
  • Plugins: Configures plugins to customize the build process.
  • Properties: Defines custom variables used throughout the POM.

Standard Directory Structure

Maven enforces a "convention over configuration" approach. By adhering to a standard directory layout, Maven knows exactly where to look for source code and where to place compiled outputs without explicit configuration.

TEXT
my-app/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/       (Application Java source code)
│   │   ├── resources/  (Application configuration files, e.g., application.properties)
│   │   └── webapp/     (Web application files, if building a WAR)
│   └── test/
│       ├── java/       (Test Java source code)
│       └── resources/  (Test-specific configuration files)
└── target/             (Generated output directory, created during build)


3. Maven Build Lifecycle Phases

Maven is built around the concept of a build lifecycle. The default lifecycle handles project deployment and consists of several phases. When you execute a phase, Maven executes all phases prior to it in order.

  1. validate: Validates that the project is correct and all necessary information is available.
  2. compile: Compiles the source code of the project (src/main/java).
  3. test: Tests the compiled source code using a suitable unit testing framework (e.g., JUnit). These tests should not require the code to be packaged or deployed.
  4. package: Takes the compiled code and packages it in its distributable format, such as a JAR or WAR.
  5. verify: Runs any checks on results of integration tests to ensure quality criteria are met.
  6. install: Installs the package into the local Maven repository (~/.m2/repository), making it available as a dependency for other local projects.
  7. deploy: Done in the build environment, copies the final package to a remote repository (like Nexus or Artifactory) for sharing with other developers and projects.

Example: Running mvn install will sequentially execute validate, compile, test, package, verify, and finally install.


4. Advanced POM and Dependency Management

Parent POM

A Parent POM is used to structure multiple Maven projects (multi-module projects) and share common configurations. Child projects inherit dependencies, plugin configurations, and properties from the Parent POM. This reduces duplication and ensures standardization across a large ecosystem of microservices or modules.

Dependency Scope

Scopes control when a dependency is available on the classpath and if it is included in the final package.

  • compile (Default): Available in all phases. Included in the final package.
  • provided: Required for compiling, but provided by the runtime environment (e.g., a Servlet API provided by Tomcat). Not packaged.
  • runtime: Not required for compilation, but needed for execution (e.g., JDBC drivers).
  • test: Only available during the test compilation and execution phases (e.g., JUnit, Mockito).
  • system: Similar to provided but requires you to provide the explicit path to the JAR on the local filesystem.
  • import: Used exclusively within <dependencyManagement> in a pom.xml to import managed dependencies from another POM.

Transitive Dependencies

When your project depends on Library A, and Library A depends on Library B, Maven automatically brings in Library B as a transitive dependency. This saves developers from having to manually discover and list all nested dependencies.

Version Conflicts & Resolution

Because of transitive dependencies, a project might pull in multiple versions of the same library (e.g., Library A needs Log4j v1.2, Library B needs Log4j v2.0). Maven resolves this using Dependency Mediation:

  • Nearest Definition: Maven uses the version of the dependency closest to the project in the dependency tree. If a direct dependency (depth 1) specifies v2.0, it overrides a transitive dependency (depth 2) specifying v1.2.
  • First Declaration Wins: If two transitive dependencies are at the same depth, the one declared first in the POM wins.
  • Exclusions: Developers can explicitly exclude unwanted transitive dependencies using the <exclusions> tag.

Using Dependency Management

The <dependencyManagement> section is typically used in a Parent POM. It does not add dependencies to a project; rather, it centralizes the versioning. If a child project declares a dependency listed in <dependencyManagement>, it can omit the <version> tag, ensuring all child projects use the exact same version.

XML
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.8</version>
        </dependency>
    </dependencies>
</dependencyManagement>


5. Maven Plugins & Execution

Maven is fundamentally a plugin execution framework; all actual work (compiling, testing, packaging) is done by plugins.

Compiler Plugin

Compiles Java source code. It allows configuration of the Java version for source and target compilation.

XML
<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>

Surefire Plugin (Unit Testing)

Runs unit tests during the test phase of the build lifecycle. It supports JUnit and TestNG and generates test reports in .txt and .xml formats in the target/surefire-reports directory.

Shade Plugin (Uber JAR)

The maven-shade-plugin packages the project along with all of its runtime dependencies into a single, executable JAR file (known as an "Uber JAR" or "Fat JAR"). This is highly useful for deploying microservices, as the resulting JAR contains everything needed to run via java -jar app.jar.

XML
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.4</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>shade</goal></goals>
        </execution>
    </executions>
</plugin>


6. Maven Wrapper (mvnw)

The Maven Wrapper (mvnw for Linux/macOS, mvnw.cmd for Windows) is a script embedded in the project source code.

  • Purpose: It allows anyone (or any CI/CD server) to build the project without having to install Maven on their machine.
  • How it works: If Maven is not installed, the wrapper automatically downloads the specific version of Maven defined in .mvn/wrapper/maven-wrapper.properties and uses it to execute the build.
  • Execution: Instead of running mvn clean install, you run ./mvnw clean install.

7. Maven and Docker Integration

Combining Maven with Docker is a standard practice in DevOps for containerizing Java applications.

dockerfile-maven-plugin

Historically, plugins like Spotify’s dockerfile-maven-plugin or Fabric8's docker-maven-plugin were used to bind Docker image creation to the Maven lifecycle (usually attaching the Docker build to the package or install phase).

  • Note: While Spotify's plugin is now deprecated, Fabric8 and Google Jib remain popular. These plugins allow developers to define Docker build parameters directly inside the pom.xml and run mvn docker:build.

Dockerizing Maven-based Applications

A modern and clean approach is using a Multi-Stage Dockerfile. This approach eliminates the need to install Maven on the host running Docker; Maven runs inside a temporary container to build the code, and only the compiled artifact is moved to the final, lightweight runtime image.

DOCKERFILE
# Stage 1: Build the application using Maven
FROM maven:3.8.4-openjdk-11-slim AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2: Create the runtime image
FROM openjdk:11-jre-slim
WORKDIR /app
# Copy the Uber JAR from the build stage
COPY --from=build /app/target/my-app-1.0.0.jar ./app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Pushing Artifacts to Registries

Artifacts must be stored securely for deployment.

  1. Maven Artifacts (JAR/WAR): Pushed to artifact repositories like Sonatype Nexus or JFrog Artifactory using the maven-deploy-plugin during the mvn deploy phase. Credentials and repository URLs are configured in ~/.m2/settings.xml and the pom.xml <distributionManagement> section.
  2. Docker Images: Once the Docker image is built from the Maven artifact, it is pushed to a container registry (like Docker Hub, Amazon ECR, or a private Nexus Docker Registry) using docker push commands, or via configuration within tools like the Fabric8 Docker Maven plugin or Google Jib.