Maven & Gradle
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:
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:
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());
}
}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:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=in.college -DartifactId=marks-app -DinteractiveMode=falseThe 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:
<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.releasesets 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.
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.AppThe 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:
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
applicationplugin adds aruntask and knows your main class. - Dependencies use configurations instead of scopes:
implementation(compile and run),runtimeOnly,testImplementationandtestRuntimeOnly. 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
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 treeOn 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?
| Maven | Gradle | |
|---|---|---|
| Build file | pom.xml (XML) | build.gradle.kts (Kotlin) or build.gradle (Groovy) |
| Style | fixed lifecycle, convention over configuration | flexible tasks written as code |
| Speed | good | faster on big projects (incremental builds, build cache) |
| Common in | enterprise and Spring projects | Android 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/orbuild/. They are generated; add them to.gitignore. - Mismatched Java versions. If
maven.compiler.releaseor the Gradle toolchain says 25 but your JDK is 21, the build fails. Checkjava -versionandJAVA_HOME. - Wrong scope. A test library without
testscope ends up in your application; a database driver needed at run time must not betest-only. - Editing files inside
target/orbuild/. 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
./mvnwor./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/javaand dependencies from Maven Central. - Maven uses
pom.xmlwith coordinates, scopes and lifecycle phases such ascompile,testandpackage. - Gradle uses
build.gradle.ktswith plugins, configurations such asimplementationandtestImplementation, and tasks such asrun,testandbuild. - 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:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=in.college -DartifactId=fee-calculator -DinteractiveMode=falsefee-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):
<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>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);
}
}mvn compile
java -cp target/classes in.college.Fees
mvn packageTotal 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?
Both tools use src/main/java for code and src/test/java for tests.
2. Which Maven scope keeps JUnit out of your packaged application?
test scope makes a dependency available only when compiling and running tests.
3. What does ./gradlew do that gradle does not?
The wrapper pins the build-tool version so every machine builds the same way.
4. Running mvn package also runs which phases first?
Maven runs every lifecycle phase up to the one you ask for.
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
