ZetCode

Maven Dependency Scopes

last modified June 10, 2025

In this article we show how to use different dependency scopes in Maven projects to control when and where dependencies are available during the build lifecycle.

Maven dependency scopes control the availability of dependencies during different phases of the build process. They determine when a dependency is included in the classpath for compilation, testing, and runtime execution.

Overview of Dependency Scopes

Maven provides six different dependency scopes that define when a dependency should be available:

Compile Scope

The compile scope is the default scope when no scope is specified. Dependencies with compile scope are available in all classpaths and are included in the final artifact.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>dependency-scopes-example</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Compile scope (default) -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.13.1</version>
            <scope>compile</scope>
        </dependency>
        
        <!-- Same as above, scope is optional for compile -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
    </dependencies>
</project>

Compile scope dependencies are used throughout the application and are packaged with the final JAR or WAR file.

src/main/java/com/example/CompileExample.java
package com.example;

import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;

public class CompileExample {
    public static void main(String[] args) {
        // Using compile scope dependencies
        Gson gson = new Gson();
        String json = gson.toJson("Hello World");
        
        String padded = StringUtils.leftPad("Maven", 10, '*');
        
        System.out.println("JSON: " + json);
        System.out.println("Padded: " + padded);
    }
}

The compile scope is the default scope for dependencies in Maven. If no scope is specified, Maven assumes compile scope.

<scope>compile</scope>

Specifies that the dependency is available during compilation, testing, and runtime phases. This is the default scope.

Runtime Scope

Dependencies with runtime scope are not required for compilation but are needed at runtime and for testing.

pom.xml (runtime dependencies)
<dependencies>
    <!-- Database driver - runtime scope -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Logging implementation - runtime scope -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.11</version>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Logging API - compile scope -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.9</version>
    </dependency>
</dependencies>
src/main/java/com/example/RuntimeExample.java
package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RuntimeExample {
    private static final Logger logger = LoggerFactory.getLogger(RuntimeExample.class);
    
    public static void main(String[] args) {
        // SLF4J API is compile scope - available at compile time
        logger.info("Starting application");
        
        // Logback implementation is runtime scope - loaded at runtime
        logger.debug("Debug message");
        logger.warn("Warning message");
        
        System.out.println("Check the logs!");
    }
}
<scope>runtime</scope>

Indicates that the dependency is not needed for compilation but is required at runtime. Common for database drivers and logging implementations.

Test Scope

Dependencies with test scope are only available during test compilation and execution phases. They are not included in the final artifact.

pom.xml (test dependencies)
<dependencies>
    <!-- JUnit for testing -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- Mockito for mocking -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>5.5.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- AssertJ for fluent assertions -->
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>3.24.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
src/test/java/com/example/CompileExampleTest.java
package com.example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.mockito.Mockito;
import static org.assertj.core.api.Assertions.assertThat;

public class CompileExampleTest {
    
    @Test
    @DisplayName("Should create JSON string")
    public void shouldCreateJsonString() {
        // Test using test scope dependencies
        CompileExample example = new CompileExample();
        
        // Using AssertJ for assertions
        assertThat(example).isNotNull();
        
        // Example of mocking with Mockito
        String mockString = Mockito.mock(String.class);
        Mockito.when(mockString.length()).thenReturn(10);
        
        assertThat(mockString.length()).isEqualTo(10);
    }
    
    @Test
    @DisplayName("Should handle string operations")
    public void shouldHandleStringOperations() {
        String result = "test";
        
        assertThat(result)
            .isNotNull()
            .hasSize(4)
            .startsWith("te")
            .endsWith("st");
    }
}
<scope>test</scope>

Specifies that the dependency is only needed for testing. These dependencies are not included in the final application package.

Provided Scope

Dependencies with provided scope are available at compile time but are expected to be provided by the runtime environment.

pom.xml (provided dependencies)
<dependencies>
    <!-- Servlet API - provided by web container -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    
    <!-- JSP API - provided by web container -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
        <scope>provided</scope>
    </dependency>
    
    <!-- Lombok - compile time only -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
src/main/java/com/example/ServletExample.java
package com.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hello")
public class ServletExample extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
                        throws ServletException, IOException {
        
        response.setContentType("text/html");
        
        try (PrintWriter out = response.getWriter()) {
            out.println("<html>");
            out.println("<head><title>Provided Scope Example</title></head>");
            out.println("<body>");
            out.println("<h1>Hello from Servlet!</h1>");
            out.println("<p>Servlet API is provided by container</p>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}
src/main/java/com/example/LombokExample.java
package com.example;

import lombok.Data;
import lombok.Builder;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LombokExample {
    private String name;
    private int age;
    private String email;
    
    public static void main(String[] args) {
        // Lombok generates getters, setters, equals, hashCode, toString
        LombokExample person = LombokExample.builder()
            .name("John Doe")
            .age(30)
            .email("john@example.com")
            .build();
            
        System.out.println("Person: " + person);
        System.out.println("Name: " + person.getName());
        System.out.println("Age: " + person.getAge());
    }
}
<scope>provided</scope>

Indicates that the dependency is needed for compilation but will be provided by the runtime environment (like a web container or JVM).

System Scope

The system scope is similar to provided but requires explicitly specifying the path to the JAR file on the local file system.

pom.xml (system scope - not recommended)
<dependencies>
    <!-- System scope dependency - not recommended -->
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc8</artifactId>
        <version>21.1.0.0</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/lib/ojdbc8.jar</systemPath>
    </dependency>
</dependencies>

System scope is generally discouraged because it makes the build non-portable. Instead, install the JAR to a local repository or use a corporate repository.

<scope>system</scope>
<systemPath>${project.basedir}/lib/ojdbc8.jar</systemPath>

System scope requires specifying the exact path to the JAR file. This makes the build environment-dependent and less portable.

Import Scope

The import scope is only used in the dependencyManagement section to import dependency management from other POMs.

pom.xml (import scope)
<dependencyManagement>
    <dependencies>
        <!-- Import Spring Boot dependency management -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.1.5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        
        <!-- Import JUnit BOM -->
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.10.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Versions managed by imported BOM -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<scope>import</scope>
<type>pom</type>

Import scope allows importing dependency management from other POM files, commonly used with Bill of Materials (BOM) POMs.

Scope Transitivity

When a dependency has its own dependencies (transitive dependencies), Maven determines their scope based on both the direct dependency scope and the transitive dependency scope.

Scope transitivity example
<dependencies>
    <!-- Spring Boot starter has many transitive dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.1.5</version>
        <scope>compile</scope>
    </dependency>
    
    <!-- Excluding specific transitive dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>3.1.5</version>
        <scope>provided</scope>
        <exclusions>
            <exclusion>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-websocket</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

Understanding scope transitivity helps you control which transitive dependencies are included in your project and when they are available.

Viewing Dependency Scopes

You can use Maven commands to analyze and understand the dependency scopes in your project.

# View dependency tree with scopes
$ mvn dependency:tree

# View dependencies by scope
$ mvn dependency:list

# Analyze dependency scope conflicts
$ mvn dependency:analyze

# Generate dependency report
$ mvn dependency:resolve-sources

These commands help you understand how Maven resolves dependencies and their scopes in your project.

Best Practices for Dependency Scopes

Here are some best practices for using dependency scopes effectively:

Source

Maven Dependency Mechanism - reference

In this article we have shown how to use different dependency scopes in Maven projects to control when and where dependencies are available.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.