Maven & Gradle

Java · Lesson 29 of 32 9 min read Updated

Up to now you have compiled programs by hand with javac and java. That works for a few files, but real projects have hundreds of classes, tests, and dozens of libraries written by other people, each with its own version. Downloading JAR files by hand and typing long class paths does not scale.

A build tool does this work for you. It downloads the libraries your project depends on, compiles your code, runs your tests and packages the result, the same way on every computer. Java has two main build tools: Maven and Gradle. Almost every Java job uses one of them, so in this lesson you will learn to read and write both.

The standard project layout

Both tools expect the same folder structure, so any Java developer can find their way around any project:

Text
marks-app/
  pom.xml                  (Maven)  or  build.gradle.kts (Gradle)
  src/
    main/
      java/
        in/college/App.java
      resources/           (config files, images)
    test/
      java/
        in/college/AppTest.java
  target/  or  build/      (output, created by the tool: never edit or commit)

Your application code goes in src/main/java and your tests in src/test/java, each in package folders. Here is the small app used in this lesson:

Java
package in.college;

import java.util.List;

public class App {
    public static void main(String[] args) {
        List<Integer> marks = List.of(78, 91, 66);
        double average = marks.stream().mapToInt(Integer::intValue).average().orElse(0);
        System.out.printf("Average of %s is %.2f%n", marks, average);
        System.out.println("Running on Java " + Runtime.version().feature());
    }
}
Output
Average of [78, 91, 66] is 78.33
Running on Java 25

Maven and pom.xml

Apache Maven describes a project in an XML file called pom.xml (Project Object Model). The current release line is Maven 3 (3.10 at the time of writing); Maven 4 is still a release candidate. Every project has coordinates: a groupId (usually your reversed domain), an artifactId (the project name) and a version.

The easiest way to start is to let Maven generate the folders and a complete pom.xml from a template, called an archetype:

Terminal
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=in.college -DartifactId=marks-app -DinteractiveMode=false

The generated file starts with an XML header and the POM model version line, which you never need to change. Below that come the parts you edit. After setting the Java version and adding two libraries, they look like this:

XML
  <groupId>in.college</groupId>
  <artifactId>marks-app</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.release>25</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>26.10.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.5</version>
      </plugin>
    </plugins>
  </build>
</project>
  • maven.compiler.release sets the Java version the code is compiled for.
  • Each <dependency> names a library by its coordinates. Maven downloads it, and everything it depends on (its transitive dependencies), from Maven Central, the main public repository of Java libraries, into a local cache in your home folder (.m2).
  • The scope says where a library is needed: compile (the default: everywhere), runtime (only when running, like a database driver), test (only for tests), provided (supplied by a server at run time).
  • The Surefire plugin runs your tests. You will use the MySQL driver in JDBC with MySQL and JUnit in Unit Testing with JUnit.

Everyday Maven commands

Maven works in lifecycle phases. Running a phase also runs every phase before it: test first compiles, package first tests.

Terminal
mvn compile          # compile src/main/java into target/classes
mvn test             # compile and run the tests
mvn package          # build target/marks-app-1.0-SNAPSHOT.jar
mvn clean package    # delete target/ first, then build from scratch
mvn dependency:tree  # show every library and where it came from
java -cp target/classes in.college.App

The archetype's pom.xml may target an older Java version and may already list a JUnit version or extra plugins. Check it, and set maven.compiler.release to 25 as shown above.

Gradle and build.gradle.kts

Gradle describes the build in code instead of XML. New projects usually use the Kotlin DSL in a file called build.gradle.kts (the older Groovy DSL uses build.gradle). Gradle is the standard build tool for Android and is popular for large projects because it is fast and flexible. The current release is Gradle 9.8.

This is the equivalent of the Maven file above, written in Kotlin syntax:

Text
plugins {
    application
}

repositories {
    mavenCentral()
}

dependencies {
    runtimeOnly("com.mysql:mysql-connector-j:26.10.0")
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

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

application {
    mainClass = "in.college.App"
}

tasks.test {
    useJUnitPlatform()
}
  • The application plugin adds a run task and knows your main class.
  • Dependencies use configurations instead of scopes: implementation (compile and run), runtimeOnly, testImplementation and testRuntimeOnly. A dependency is written as one string, "group:artifact:version".
  • platform("org.junit:junit-bom:6.1.3") imports a BOM (bill of materials) that fixes matching versions for all JUnit parts, so the next lines need no version.
  • The toolchain block tells Gradle which JDK to compile with; Gradle can find or download a matching JDK even if you run Gradle itself on another version.
  • useJUnitPlatform() tells the test task to run JUnit 5 and 6 style tests.

Everyday Gradle commands

Terminal
gradle init --type java-application --dsl kotlin   # create a new project (asks a few questions)
./gradlew run          # compile and run the application
./gradlew test         # run the tests
./gradlew build        # compile, test and package into build/
./gradlew clean build  # start from scratch
./gradlew dependencies # show the dependency tree

On Windows, use gradlew.bat (or .\gradlew in PowerShell) instead of ./gradlew.

Wrappers: the same version for everyone

Notice ./gradlew instead of gradle. The wrapper is a small script plus a properties file, committed to the project, that downloads and runs the exact build-tool version the project expects. Teammates and build servers then need only a JDK, not a separately installed Gradle. Maven has the same idea: mvn wrapper:wrapper creates mvnw and mvnw.cmd, and from then on you run ./mvnw package. Always prefer the wrapper when a project has one.

Maven or Gradle?

MavenGradle
Build filepom.xml (XML)build.gradle.kts (Kotlin) or build.gradle (Groovy)
Stylefixed lifecycle, convention over configurationflexible tasks written as code
Speedgoodfaster on big projects (incremental builds, build cache)
Common inenterprise and Spring projectsAndroid and large multi-project builds

Both use Maven Central, the same folder layout and the same dependency coordinates, so skills transfer easily. Use whichever your team or course uses; for a first project, Maven's fixed structure is slightly easier to understand.

Common mistakes

  • Committing target/ or build/. They are generated; add them to .gitignore.
  • Mismatched Java versions. If maven.compiler.release or the Gradle toolchain says 25 but your JDK is 21, the build fails. Check java -version and JAVA_HOME.
  • Wrong scope. A test library without test scope ends up in your application; a database driver needed at run time must not be test-only.
  • Editing files inside target/ or build/. They are overwritten on the next build; change the sources instead.
  • Ignoring the wrapper. A different Maven or Gradle version may build the project differently; use ./mvnw or ./gradlew.

Key points

  • Build tools download dependencies, compile, test and package projects in a repeatable way.
  • Both Maven and Gradle use src/main/java, src/test/java and dependencies from Maven Central.
  • Maven uses pom.xml with coordinates, scopes and lifecycle phases such as compile, test and package.
  • Gradle uses build.gradle.kts with plugins, configurations such as implementation and testImplementation, and tasks such as run, test and build.
  • Wrappers (mvnw, gradlew) pin the build-tool version for everyone.

Exercise

Create a Maven project for a small app in.college.Fees that prints the total fee for a list of three courses (for example 12000, 8500 and 15000 rupees). Generate it with the archetype using groupId in.college and artifactId fee-calculator, then edit the pom.xml for Java 25 and JUnit 6.1.3 as a test dependency. List the folders, then give the commands to compile, run and package it.

Show solution

Generate the project, then replace the generated App.java with Fees.java:

Terminal
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=in.college -DartifactId=fee-calculator -DinteractiveMode=false
Text
fee-calculator/
  pom.xml
  src/main/java/in/college/Fees.java
  src/test/java/in/college/        (tests go here later)

The edited part of pom.xml (below the generated header lines):

XML
  <groupId>in.college</groupId>
  <artifactId>fee-calculator</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.release>25</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>
Java
package in.college;

import java.util.List;

public class Fees {
    public static void main(String[] args) {
        List<Integer> fees = List.of(12000, 8500, 15000);
        int total = fees.stream().mapToInt(Integer::intValue).sum();
        System.out.println("Total fee: Rs " + total);
    }
}
Terminal
mvn compile
java -cp target/classes in.college.Fees
mvn package
Output
Total fee: Rs 35500

mvn package creates target/fee-calculator-1.0-SNAPSHOT.jar. Before you add tests, also add the Surefire plugin block from the lesson so JUnit 6 tests run.

Quick Quiz

1. Where does application source code go in a Maven or Gradle project?

2. Which Maven scope keeps JUnit out of your packaged application?

3. What does ./gradlew do that gradle does not?

4. Running mvn package also runs which phases first?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call