How to Copy Both JARs and Source JARs in a Single Maven Command
When managing multi-module Maven projects with dozens or even hundreds of submodules, build performance and developer ergonomics are paramount. While mvn dependency:copy-dependencies efficiently copies standard library JARs into your target directory, capturing both the binary JARs and their corresponding source JARs (-sources.jar) typically forces developers into running two separate commands.
In large projects, running the reactor twice can add several painful minutes to your iteration loop. Fortunately, you can bundle both operations into a single command pass. Here is how to achieve it cleanly and efficiently.
The Core Problem: Why the Command Line Flags Fall Short
The maven-dependency-plugin exposes the parameter -Dclassifier=sources. However, this property acts as a filter override rather than an additive list. Setting it tells Maven to look only for artifacts with that specific classifier instead of the primary artifacts.
Furthermore, Maven properties passed via the CLI (-D...) apply globally across the execution, making it impossible to invoke mvn dependency:copy-dependencies twice in a single CLI string with different classifier values.
Solution 1: Configure Multiple Executions in Your pom.xml (Recommended)
The cleanest, most maintainable solution for multi-module projects is defining two separate executions under the maven-dependency-plugin in your parent pom.xml. This allows Maven to process both binaries and sources in a single build pass.
Add the following configuration to your pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0">... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.6.1</version> <executions> <!-- Execution 1: Standard Binaries --> <execution> <id>copy-binary-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/dependencies</outputDirectory> </configuration> </execution> <!-- Execution 2: Source Attachments --> <execution> <id>copy-source-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <classifier>sources</classifier> <!-- Important: prevent failures when a 3rd-party dependency lacks sources --> <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact> <outputDirectory>${project.build.directory}/dependencies</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build></project>Running the Single Command
With this configuration in place, you can simply run:
mvn packageOr, if you do not want to run through test and packaging phases every time, bind both executions to a lightweight lifecycle phase like generate-resources and run:
mvn generate-resourcesSolution 2: Isolating with a Dedicated Profile
If you don't want to copy all dependencies during every regular build, wrap the executions inside a Maven profile. This gives you on-demand execution in a single command without slowing down daily builds.
<profiles> <profile> <id>fetch-all-deps</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.6.1</version> <executions> <execution> <id>copy-binaries</id> <phase>generate-resources</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/dependencies</outputDirectory> </configuration> </execution> <execution> <id>copy-sources</id> <phase>generate-resources</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <classifier>sources</classifier> <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact> <outputDirectory>${project.build.directory}/dependencies</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile></profiles>Now you can trigger both downloads and copies across all 100+ modules in a single pass with:
mvn generate-resources -Pfetch-all-depsCritical Tip: failOnMissingClassifierArtifact
Notice the configuration tag <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>. By default, Maven will fail the build if it encounters a transitive dependency that does not have a companion -sources.jar published in the remote repository. Setting this property to false ensures the build completes smoothly even if some sources are missing.
Summary
- Do not use pure CLI arguments: Global properties like
-Dclassifiercannot be toggled halfway through a single CLI invocation. - Use multiple plugin executions: Configuring two distinct
<execution>blocks inside the parentpom.xmlexecutes both tasks within a single reactor traversal. - Prevent build breaks: Always set
failOnMissingClassifierArtifacttofalsewhen working with external sources.