انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-054-L03

الاعتماديات وMaven Central

  • الدرس 3 من 6
  • 10 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

Lesson 1 showed how painful it is to hunt for library JARs by hand. This lesson shows the Maven answer: you write down the library's coordinates in the pom.xml, and Maven fetches it for you from a public warehouse called Maven Central, keeps a copy on your machine, and even brings along the libraries that library needs.

In the recipe analogy, you now write the ingredient list, and the kitchen assistant goes shopping.

Why this matters: adding a library is something you will do every week as a backend developer. This lesson builds on lesson 2 (coordinates and the pom.xml), and the test scope introduced here is exactly how JUnit from module BEG-045 will be added to Maven projects.

Learning Objectives

By the end of this lesson you will be able to:

  • Write a <dependency> block from a library's coordinates.
  • Explain what Maven Central and the local repository are, and how they work together.
  • Find a library's coordinates on its Central page and choose a version deliberately.
  • Explain the difference between the compile and test scopes.
  • Describe transitive dependencies and show them with mvn dependency:tree.
  • Recognize the error for a version that does not exist.

Written Explanation

Declaring a dependency

A dependency in a Maven project is one <dependency> block inside <dependencies>:

xml
<dependencies>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.21.0</version>
  </dependency>
</dependencies>

The three fields are the library's coordinates, the same idea as your project's own (lesson 2). Together they say: "I need org.apache.commons:commons-lang3, version 3.21.0." After saving, you can import org.apache.commons.lang3.StringUtils; in your code. Maven puts the library on the class path for compiling, testing, and (for packaging) running. No downloading, no -cp.

Maven Central and the local repository

Where does Maven get the library? From a repository, a place that stores libraries by their coordinates. There are two you must know:

  • Maven Central is the large public repository, and Maven's default remote repository. Most open-source Java libraries are published there.
  • The local repository is a folder on your machine, by default .m2/repository in your home directory. Maven keeps every downloaded file there.

The flow is simple. The first time a build needs a library, Maven downloads it from Central into the local repository. Later builds find the cached copy and download nothing. Real output of the first build after adding Commons Lang:

text
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar (749 kB at 2.4 MB/s)

A second mvn test on the project printed no "Downloading" lines at all, and mvn -o test (the -o flag means offline: do not touch the network) still gave BUILD SUCCESS. Look at the URL: the coordinates became the path org/apache/commons/commons-lang3/3.21.0/. The local repository uses the same layout, so you can look inside it.

Looking inside the local repository

The local repository is an ordinary folder, so you can open it. For Commons Lang 3.21.0 it contains a folder org/apache/commons/commons-lang3/3.21.0/ with these files (real listing, shortened):

text
commons-lang3-3.21.0.jar
commons-lang3-3.21.0.jar.sha1
commons-lang3-3.21.0.pom
commons-lang3-3.21.0.pom.sha1

The .jar is the library itself. The .pom is that library's own pom.xml, which is how Maven learns its dependencies. The .sha1 files are checksum files (used to check downloads; I did not verify the exact mechanism). You never edit these files. If a download ever seems corrupted, deleting that one version folder makes Maven fetch it again at the next build; deleting the whole local repository is safe too, but the next build will be slow while everything is downloaded again. The same repository is shared by all your Maven projects, so a library used by ten projects is stored once.

Finding coordinates

You do not guess coordinates. Open the library's page on Maven Central (central.sonatype.com) or read the library's own documentation, which almost always shows a ready-to-copy dependency snippet. The page for Commons Lang shows org.apache.commons, commons-lang3, and a version. Copy all three fields exactly.

Choosing the version: take a current release from the Central page. Do not use versions from old blog posts without checking: they work, but may lack fixes. And do not copy a version number from this lesson blindly either; newer ones exist by the time you read it. Once you chose a version, you keep it fixed so every machine gets the same library.

Scope

Not every dependency is needed everywhere. The optional <scope> says when a dependency is on the class path:

ScopeAvailable forTypical example
compile (default)Compiling, testing, and runningA helper library your code uses
testOnly compiling and running the testsJUnit
runtimeRunning and testing, not compiling your codeA database driver, later
providedCompiling and testing; supplied by the environment at run timeRare for beginners

The default, when you write no scope, is compile. JUnit is only needed to run tests, so it gets <scope>test</scope>, as in the project of lesson 2:

xml
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.12.2</version>
  <scope>test</scope>
</dependency>

A test dependency is not available to code in src/main/java, and is not passed on to projects that depend on yours. This keeps the production JAR free of testing tools.

Transitive dependencies

Libraries depend on libraries. When you declare junit-jupiter, Maven reads that library's own POM and brings in the libraries it needs, and theirs, automatically. These are transitive dependencies. You can see the whole tree with a command (real output from the example project):

text
$ mvn dependency:tree
[INFO] com.example:hello:jar:1.0-SNAPSHOT
[INFO] \- org.junit.jupiter:junit-jupiter:jar:5.12.2:test
[INFO]    +- org.junit.jupiter:junit-jupiter-api:jar:5.12.2:test
[INFO]    |  +- org.opentest4j:opentest4j:jar:1.3.0:test
[INFO]    |  +- org.junit.platform:junit-platform-commons:jar:1.12.2:test
[INFO]    |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO]    +- org.junit.jupiter:junit-jupiter-params:jar:5.12.2:test
[INFO]    \- org.junit.jupiter:junit-jupiter-engine:jar:5.12.2:test
[INFO]       \- org.junit.platform:junit-platform-engine:jar:1.12.2:test

One declared line produced eight JARs. Doing that by hand is the nightmare from lesson 1. The test scope you set on the top line is inherited by all the transitive ones. Maven's documentation recommends declaring the libraries your code uses directly, rather than relying on something else to pull them in.

A version that does not exist

A typo in coordinates is the most common mistake. Real output after changing the version to 9.9.9:

text
[ERROR] Failed to execute goal on project hello: Could not resolve dependencies for project com.example:hello:jar:1.0-SNAPSHOT
[ERROR] dependency: org.apache.commons:commons-lang3:jar:9.9.9 (compile)
[ERROR] 	Could not find artifact org.apache.commons:commons-lang3:jar:9.9.9 in central (https://repo.maven.apache.org/maven2)

Read it from the bottom up: Maven looked for those exact coordinates in Central and found nothing. Check the spelling and the version on the Central page.

Where the analogy stops

A kitchen assistant fetches ingredients from the shop of your choice. Maven uses Central by default, and a company can add its own private repository. A public repository is also not a guarantee of quality: you are responsible for choosing libraries you trust, a topic later modules return to.

Examples

Example 1: add a library and use one class (simple)

Add the Commons Lang block from this lesson to a project's <dependencies>, then create this class:

java
package com.example;

import org.apache.commons.lang3.StringUtils;

public class Shout {
    public static void main(String[] args) {
        System.out.println(StringUtils.capitalize("maven central works"));
        System.out.println(StringUtils.reverse("abc"));
    }
}

mvn compile downloaded the JAR (lines shown above) and ended with BUILD SUCCESS. Running the compiled class with a manual class path produced:

text
Maven central works
cba

Note the honest detail: mvn compile only builds. If you run java -cp target/classes com.example.Shout without adding the library's JAR to the class path, you get the NoClassDefFoundError from lesson 1. Maven puts the libraries on the class path for its own steps; running the packaged program is a later topic.

Example 2: read a dependency tree (practical)

In the tree above, which entry is the one you declared? The top one, junit-jupiter. Everything below it is transitive. If a classmate asks "why is opentest4j on my class path?", the answer is: because JUnit needs it, and Maven fetched it.

Example 3: choose the scope (realistic)

A project uses Commons Lang inside its code and JUnit in its tests. The pom.xml has:

xml
<dependency>   <!-- commons-lang3: no scope, so compile -->
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.21.0</version>
</dependency>
<dependency>   <!-- JUnit: test scope -->
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.12.2</version>
  <scope>test</scope>
</dependency>

In the real dependency:tree of that combined project, Commons Lang appears with compile and the JUnit line with test. If a class in src/main/java tried to import org.junit.jupiter.api.Test, compilation would fail, because that scope is not available there.

Backend Insight

A typical backend service declares many dependencies: a web framework, a database driver, a JSON library, and a test library. Each has coordinates and a fixed version, and the project's behavior depends on those choices. Fixed versions make builds repeatable; checking a library's current release and its trustworthiness is part of professional practice, because every dependency, and each of its own dependencies, is code that runs in your service.

Common Mistakes

Mistake 1: Forgetting <scope>test</scope> on JUnit

  • Incorrect: JUnit with no scope. It works, but the testing library is treated as part of the application's compile dependencies.
  • Correct: <scope>test</scope> so it is only used for testing.

Mistake 2: A typo in the coordinates or version

The error is "Could not find artifact ... in central". Copy the block from the library's page instead of typing it.

Mistake 3: Copying a very old version from a blog

Always check the current release on Central, then pin that version.

Mistake 4: Downloading the JAR by hand "to be safe"

Maven already downloads and caches it. A hand-copied JAR in the project is the lesson-1 problem again.

Mistake 5: Assuming mvn compile gives you a runnable program with libraries

The compile step builds your classes and checks them against the libraries. Running the program needs those libraries on the class path as well.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. What three values do you need to declare a dependency?

أظهر الحل A1

A1. groupId, artifactId, version (the library's coordinates).

A2. What is the difference between Maven Central and the local repository?

أظهر الحل A2

A2. Central is the public online repository where libraries are published. The local repository is a folder on your machine where Maven caches the files it downloaded from Central.

A3. Which scope is right for JUnit, and why?

أظهر الحل A3

A3. test, because JUnit is only needed to compile and run tests, not in the application itself.

Level B: Application

B1. Write the <dependency> block for org.apache.commons:commons-lang3 version 3.21.0 with the default scope.

أظهر الحل B1

B1.

xml
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.21.0</version>
</dependency>

B2. After mvn compile succeeds once, you run it again and nothing is downloaded. Explain why, and what the -o flag adds.

أظهر الحل B2

B2. The library is already in the local repository, so Maven reuses it. -o (offline) makes Maven avoid the network completely; the build only works if everything needed is already cached.

B3. In the dependency tree above, how many JARs did declaring one dependency bring? Which ones are transitive?

أظهر الحل B3

B3. Eight JARs. Only junit-jupiter was declared; the other seven (junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine, opentest4j, junit-platform-commons, apiguardian-api, junit-platform-engine) are transitive.

Level C: Challenge

C1. Add Commons Lang to a Maven project of your own, use StringUtils.reverse on a string, and run it. Write down the exact command(s) and the output. (Use mvn compile, then a manual class path or your IDE.)

أظهر الحل C1

C1. Example: after adding the dependency and a class like Shout, mvn compile ends with BUILD SUCCESS; running from the IDE or with the manual class path prints cba for reverse("abc"). Your exact output depends on your string.

C2. A colleague writes a version that does not exist and reads: "Could not find artifact". List three checks, in order, that you would do.

أظهر الحل C2

C2. (1) Read the coordinates in the error message and compare them letter by letter with the library's Central page. (2) Check that the version number exists on the Versions list there. (3) Check network access and that no company repository setting redirects Maven, then run the build again.

Mini Challenge

Choose a library you have never used from Maven Central (for example another Apache Commons library), open its page, and copy its dependency snippet into a scratch project's pom.xml. Run mvn compile and mvn dependency:tree. Write down: the coordinates you chose and where you found the version, and whether it brought any transitive dependencies. If the tree shows none, say so.

Check your work: the build ends with BUILD SUCCESS, and you can say which line in the tree you declared yourself.

Summary & Next Lesson

Key takeaways

  • A dependency is declared by its coordinates in <dependencies>; Maven downloads it from Maven Central and caches it in the local repository.
  • Copy coordinates from the library's Central page and pick a current version deliberately.
  • <scope>test</scope> keeps tools such as JUnit out of the application; compile is the default.
  • Dependencies bring their own dependencies (transitive); mvn dependency:tree shows them.
  • A "Could not find artifact" error means wrong coordinates or version.

Next lesson

The next lesson, "The build lifecycle", explains the ordered phases (compile, test, package) behind the commands you have been running.

Self-check

  1. Where does Maven download libraries from by default?
  2. Where does it keep them afterwards?
  3. What does <scope>test</scope> mean?
  4. What is a transitive dependency?
  5. What does "Could not find artifact" usually mean?
أظهر الإجابات
  1. Maven Central.
  2. In the local repository, .m2/repository in your home directory by default.
  3. The dependency is available only when compiling and running tests.
  4. A library that one of your dependencies needs; Maven brings it automatically.
  5. The coordinates or version are wrong, or not reachable from the repository.

References

  • Apache Maven, Introduction to Repositories. Supports Central as the default remote repository, the local repository as a cache of downloads, on-demand downloading, and offline builds with -o. The default .m2/repository location comes from my own machine, not from this page.
  • Apache Maven, Introduction to the Dependency Mechanism. Supports transitive dependencies, the compile, provided, runtime, and test scopes, test not being transitive, and mvn dependency:tree.
  • Maven Central, commons-lang3 page. Shows the coordinates and a ready-made dependency snippet (version 3.21.0 at the time I looked).
  • All outputs were produced with Maven 3.9.11 on JDK 21. Library versions shown will be outdated eventually; check Central.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.