Packages, Maven and Gradle

Java · Practical · 14 min

Every real Java project uses packages and a build tool to compile, manage dependencies, test and package.

A package groups related classes and gives them a namespace. Declare it at the top of the file (package com.example.billing;) and place the file in the matching directory, com/example/billing/. Package names are reversed domain names in lowercase. Use import to refer to classes from other packages by their simple name; java.lang is imported automatically.

At runtime the JVM finds classes on the classpath: a list of directories and JAR files (zip archives of .class files plus metadata). Managing that list by hand quickly becomes painful, which is why everyone uses a build tool.

Maven and Gradle both:

  • download dependencies (and their transitive dependencies) from Maven Central by coordinates groupId:artifactId:version,
  • use the standard layout: src/main/java, src/main/resources, src/test/java,
  • compile, run tests and package a JAR with one command.

Maven is configured declaratively in pom.xml and runs lifecycle phases: mvn package compiles, tests and builds the JAR into target/. Gradle uses a Kotlin or Groovy build script (build.gradle.kts) and tasks: ./gradlew build puts output in build/. Both projects normally commit a wrapper (mvnw, gradlew) so everyone uses the same tool version.

In Maven, dependency scope matters: test dependencies like JUnit are available only to tests. In Gradle the equivalent is testImplementation versus implementation.

Set the Java version explicitly — <maven.compiler.release>21</maven.compiler.release> in Maven, or a Java toolchain in Gradle — so builds don't depend on whichever JDK happens to be installed.

Example

// File: src/main/java/com/example/billing/Invoice.java
package com.example.billing;

import java.math.BigDecimal;
import java.util.List;

public record Invoice(String id, List<BigDecimal> lines) {
    public BigDecimal total() {
        return lines.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

// pom.xml (excerpt):
//   <properties><maven.compiler.release>21</maven.compiler.release></properties>
//   <dependency>
//     <groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId>
//     <version>5.11.0</version><scope>test</scope>
//   </dependency>
// Build and test:  ./mvnw package     or     ./gradlew build

Remember

  • Package names mirror directories: com.example.app → com/example/app/
  • Build tools resolve dependencies by group:artifact:version and use a standard layout
  • Commit the wrapper and pin the Java release for reproducible builds

Watch out for

  • Putting a file in a directory that doesn't match its package declaration, which leads to "class not found" errors.

Check yourself

  1. In Maven, what does `<scope>test</scope>` on a dependency mean?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: maven.apache.org/guides/getting-started/