Other Tutorials

Maven Dependency Scopes

Introduction:

Managing dependencies is a core feature of Maven. When defining a maven dependency, the scope attribute defines the visibility of that dependency on different maven lifecycle phases such as build, test and run.

The dependency scopes limit the transitivity of dependencies in any project, thereby affecting the classpath. Also, there are six different available dependency scopes in Maven. In this tutorial, we’ll learn about each of them.

Transitive Dependencies:

A dependency in Maven can be either direct or transitive.

A direct dependency is the one that we explicitly define our POM file:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

On the other hand, the transitive dependencies are the ones required by the direct dependencies. The required transitive dependencies are automatically included in our maven project classpath.

Key Terms:

Before we dive in to learn the available maven scopes, let’s first refresh our knowledge on a few key terms we’ll use-

  • compile-classpath: have dependencies for our project’s main source files
  • test-classpath: holds dependencies that are present during test compilation and execution
  • runtime-classpath: dependencies that’ll be available in the runtime environment and their executions

Dependency Scopes:

Let’s now look at different Maven Dependency Scopes:

1. Compile:

It is the default scope in Maven unless specified otherwise. We need these dependencies for the main source code compilation. Also, the compile dependencies are available in all of the three classpaths we discussed above.

These dependencies are transitive and so propagated to the dependent projects.

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2. Test:

Dependencies with a scope set as test are only available in the test-classpath. We only need these dependencies to compile and run the unit test cases. Also, it isn’t transitive.

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

3. Runtime:

We don’t need a runtime dependency for the code compilation but very much need it for the code execution at runtime. Such a dependency isn’t available in the compile-classpath but is present in the test-classpath as well as the runtime-classpath.

A good example of it would be a dependency for a JDBC driver:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
    <scope>runtime</scope>
</dependency>

It is transitive. So, the project packaging includes all the transitive dependencies.

4. Provided:

A provided dependency is only available in compile-classpath and test-classpath. It assumes that the runtime environment like JDK, a web or a servlet container etc will provide the dependent JARS in the deployed mode.

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>

It isn’t transitive in nature. So after packaging, we won’t be able to find either direct or transitive dependencies of this scope in our lib folder.

5. System:

The system scope is pretty much similar to the provided scope. Here, the only difference is that it refers to the dependent jars from a path in our local system.

<dependency>
    <groupId>com.programmergirl</groupId>
    <artifactId>my-dependency</artifactId>
    <version>2.5.1</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/libs/my-dependency-2.5.1.jar</systemPath>
</dependency>

One drawback of using this scope is that we must do a manual setup on each system before execution. This is so as it doesn’t look up to the maven local repository. Rather it picks up the JARS provided in the systemPath. As expected, here the transitivity doesn’t come into the picture.

6. Import:

Import dependency scope was first introduced in Maven 2.0.9. It’s useful for a multi-module maven based projects. To work with it, we need to configure <dependencyManagement> in our parent POM. Also, it’s only supported on a dependency of type – pom in the <dependencyManagement> section.

The idea is to be able to import managed dependencies from other projects. So in our effective POM, an imported dependency gets replaced with all the effective dependencies available in that project.

<dependencyManagement>
    <dependencies>
         <dependency>
              <groupId>com.programmergirl</groupId>
              <artifactId>my-project</artifactId>
              <version>1.1</version>
              <type>pom</type>
              <scope>import</scope> 
         </dependency>
    </dependencies>
</dependencyManagement>

Transitivity Resolution Rules:

Our main packaged project doesn’t include project dependencies with scope – provided and test. It is so because they’re transitive in nature. For resolving transitivity for direct dependencies with a given scope, we have following rules:

  • compile – pulls in the transitive dependencies with runtime and compile scope without changing their scope
  • test – both compile and runtime transitive dependencies are pulled in with the test scope in our project
  • runtime – pulls both compile and runtime transitive dependencies with the runtime scope in the project
  • provided – both compile and runtime scope dependencies will be pulled in with the provided scope

Conclusion:

In this tutorial, we covered all six types of dependency scopes available in Maven. We also talked about the transitive dependencies and their resolution based on the maven scopes.

3 comments
Pingback: Maven Dependency Scopes | Hey Android - Android World

Leave a Comment

Your email address will not be published. Required fields are marked *