On this page

Maven Fundamentals & Why Maven Exists

Goal: Understand why Maven was created before learning commands.

Throughout this page, we will follow one small project: a Student Report App. It reads student marks and saves a report as a JSON file. Simple idea, but it is enough to show every problem Maven solves.

What is Maven?

Maven is a tool that manages and builds Java projects for you.

It does three main jobs:

  1. Downloads the libraries your project needs.
  2. Builds your project: compiles the code, runs the tests, and packages everything into a single file.
  3. Gives every project the same structure, so any Java developer can open your project and know where things are.

You describe your project in one file called pom.xml. Maven reads it and does the rest.

For example, our Student Report App needs a library called Jackson to create JSON. With Maven, we just write this in pom.xml:

xml
1<dependency>
2 <groupId>com.fasterxml.jackson.core</groupId>
3 <artifactId>jackson-databind</artifactId>
4 <version>2.17.0</version>
5</dependency>

Maven downloads Jackson, plus everything Jackson itself needs.

That might not sound impressive yet. To see why it matters, let's look at what building a Java project involves without Maven.

Why do we need a build/project management tool?

Writing code is only part of the work. To turn the Student Report App into something you can run or share, you have to:

  1. Find and download the libraries it uses (like Jackson).
  2. Compile every .java file.
  3. Run the tests.
  4. Package the compiled code into a file you can share.
  5. Do all of this again every time the code changes.

Doing these steps by hand is slow, and it is easy to forget one. It gets worse in a team:

  • Each developer may download a different library version.
  • Each developer may build the project a different way.
  • The project works on one laptop and fails on another.

A build tool solves this by writing the steps down once. After that, everyone builds the project the same way with one command, on any machine.

Maven is one such tool, and one of the most widely used in Java.

Maven vs manual JAR management

Before Maven, Java developers managed libraries by hand. Here's what adding Jackson to our app looks like both ways:

TaskWithout MavenWith Maven
Get a librarySearch the web, download the .jar fileAdd a few lines to pom.xml
Get the library's own needsFind and download each one yourselfDownloaded automatically
Make Java find themBuild a long classpath by handDone automatically
Upgrade a versionDelete old files, download new ones, fix the classpathChange one version number
Share with your teamCommit .jar files or send them aroundShare only pom.xml

Don't worry if words like JAR, dependency, and classpath are new. The rest of this page explains each one, and by the end you will see exactly why the "Without Maven" column becomes painful.

Let's start with the file that appears in almost every row: the JAR.

What is a .jar file?

A JAR (Java ARchive) is a single file that bundles many compiled Java files together.

It is basically a zip file. If you rename app.jar to app.zip and open it, you can see what's inside:

text
1student-report.jar
2├── com/example/Main.class
3├── com/example/Student.class
4├── com/example/ReportWriter.class
5└── META-INF/MANIFEST.MF
  • The .class files are your compiled code.
  • MANIFEST.MF is a small text file with information about the JAR.

Instead of sharing hundreds of separate files, you share one JAR.

But where do those .class files come from? That's the next step.

.java → .class → .jar

Your code goes through three stages:

text
1Main.java → Main.class → student-report.jar
2(you write) (compiled) (packaged)

1. .java: the code you write. It is plain text that humans can read.

2. .class: compiled code. The javac compiler turns each .java file into a .class file containing bytecode. The Java Virtual Machine (JVM) runs bytecode, not .java files.

text
1javac -d out src/com/example/*.java

3. .jar: packaged code. All the .class files are bundled into one JAR.

text
1jar cf student-report.jar -C out .

So a JAR is simply the final, shareable form of Java code.

Our Student Report App ends up as a JAR. Jackson is also a JAR. But they are very different kinds of JAR, which brings us to the next idea.

Library vs Application

An application is a program you run. It has a starting point: the main method.

Our Student Report App is an application. You run it, and it produces a report.

A library is code written for other programs to use. You don't run it on its own. It has no main method meant for you to start.

Jackson is a library. It knows how to turn Java objects into JSON, but it does nothing until an application calls it.

Think of a car:

  • The application is the car you drive.
  • The libraries are parts like the engine and tyres. They are essential, but you don't drive an engine by itself.

Our app uses Jackson so we don't have to write JSON code ourselves. Most real applications use many libraries this way.

Executable JAR vs normal/library JAR

Since there are two kinds of code, there are two kinds of JAR.

An executable JAR contains an application and can be started directly:

text
1java -jar student-report.jar

This works because its MANIFEST.MF says which class to start from:

text
1Main-Class: com.example.Main

A normal (library) JAR has no Main-Class entry. You can't run it. Its only job is to provide code to other programs, like jackson-databind.jar.

Executable JARLibrary JAR
Examplestudent-report.jarjackson-databind.jar
Has Main-Class in manifestYesNo
Can run with java -jarYesNo
PurposeStart an applicationBe used by other code

Our executable JAR still needs Jackson's code to work. Something our code needs like this has a name: a dependency.

What is a dependency?

A dependency is a library your project needs in order to compile or run.

Our Student Report App depends on Jackson. Without Jackson, it can't create JSON.

Here's the catch: libraries have dependencies too. jackson-databind itself needs two more libraries:

text
1student-report-app
2└── jackson-databind
3 ├── jackson-core
4 └── jackson-annotations
  • Direct dependency: a library you chose to use (jackson-databind).
  • Transitive dependency: a library that your library needs (jackson-core, jackson-annotations).

We only asked for one library, but our app actually needs three JARs.

So our app needs all three JARs. But having them in a folder is not enough. Java has to be told where to find them, and it does that through the classpath.

What is the classpath?

The classpath is the list of places where Java looks for .class files when your program runs.

When our app calls Jackson, the JVM searches the classpath for Jackson's classes. If it can't find them, the program crashes with an error like:

text
1java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper

Think of the classpath as a shopping list for the JVM. Anything not on the list, the JVM won't find, even if the file is sitting right there on your computer.

Without Maven, you write this list yourself:

text
1java -cp "out:lib/jackson-databind.jar:lib/jackson-core.jar:lib/jackson-annotations.jar" com.example.Main

(On Windows, use ; instead of : between paths.)

That's three JARs for one library. Now imagine a real project.

Why manual dependency management becomes difficult

Let's put everything together. Without Maven, adding one library to our app means:

  1. Finding jackson-databind.jar online and downloading it.
  2. Discovering it needs jackson-core and jackson-annotations, and downloading those too.
  3. Making sure all three versions work together.
  4. Adding all three to the classpath.
  5. Sharing all three with your team.

That's for one library. Real projects often use dozens of libraries, which pull in many more transitive ones. Now the problems multiply:

Hidden dependencies. You often find out a JAR is missing only when the program crashes with a NoClassDefFoundError.

Version conflicts. Suppose two libraries both need jackson-core, but different versions. Only one version can be on the classpath, so you must figure out which one works for both.

Long, fragile classpaths. One typo in a long classpath, and the app fails to start.

Painful upgrades. Upgrading one library can mean replacing several JARs and fixing the classpath again.

Team mismatch. Everyone needs the exact same JARs. If one developer has a different version, the app behaves differently on their machine.

This is the problem Maven was created to solve.

Remember the few lines we wrote in pom.xml at the start? With them, Maven:

  • downloads jackson-databind,
  • finds and downloads jackson-core and jackson-annotations automatically,
  • picks versions that work together,
  • builds the classpath for you,
  • and gives your whole team the exact same setup from one shared file.

Now that you know why Maven exists, the next step is learning how to use it.

Next TopicMaven Project Structure & pom.xml