Scopes decide what ends up on the compile classpath and in your packaged artifact.
Dependencies are listed under <dependencies>, each with coordinates and an optional scope:
compile(default): needed to compile, test and run; propagates to consumers of your artifact.provided: needed to compile but supplied at run time by the container or platform (servlet API, Lombok, annotation processors). Not packaged, not transitive.runtime: not needed to compile, needed to run and test (JDBC drivers, logging implementations).test: only for compiling and running tests (JUnit, Mockito, AssertJ).import: only independencyManagement, to import a BOM (next lessons).systemis deprecated; avoid it.
Maven downloads artifacts from Maven Central by default (plus any <repositories> you add) into the local repository ~/.m2/repository, which is shared by all your projects. mvn dependency:tree prints the resolved graph, mvn dependency:analyze flags used-but-undeclared and declared-but-unused dependencies, and mvn dependency:resolve -U forces a re-check of snapshots.
Transitive dependencies are included automatically with scopes adjusted: a compile dependency's runtime dependencies become runtime for you, and test/provided ones are not inherited at all.
Prefer fixed versions. Version ranges ([1.2,2.0)) make builds non-reproducible. Keep versions in <properties> (<junit.version>5.11.4</junit.version>) or in dependencyManagement so they're declared once.
Example
<properties>
<junit.version>5.11.4</junit.version>
</properties>
<dependencies>
<dependency> <!-- compile scope (default) -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.3</version>
</dependency>
<dependency> <!-- only at run time -->
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.5</version>
<scope>runtime</scope>
</dependency>
<dependency> <!-- compile-time only, not packaged -->
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- mvn dependency:tree mvn dependency:analyze -->Remember
- compile is the default and propagates to consumers
- provided and runtime split compile-time from run-time needs
- test scope keeps test libraries out of the artifact
- dependency:tree and dependency:analyze inspect the graph
Watch out for
- Declaring a JDBC driver with compile scope and accidentally coding against driver-specific classes.