Downloading Maven Artifacts from a POM file programmatically with Eclipse Aether
September 8th, 2017 by Micha KopsSometimes I need to resolve Maven dependencies programmatically. Eclipse Aether is a library for working with artifact repositories and I’ll be using it in the following example to read dependency trees from a given POM descriptor file and download each dependency from a remote Maven repository to a local directory.
Contents
Dependencies
We’re adding a bunch of dependencies to our project’s pom.xml:
<properties> <aetherVersion>1.1.0</aetherVersion> <mavenVersion>3.2.1</mavenVersion> </properties>
<dependencies> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-api</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-spi</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-util</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-impl</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-connector-basic</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-transport-file</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.eclipse.aether</groupId> <artifactId>aether-transport-http</artifactId> <version>${aetherVersion}</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-aether-provider</artifactId> <version>${mavenVersion}</version> </dependency> </dependencies>
Artifact Downloader
This is our sample application that reads in its own pom.xml, initializes its repository systems, creates a model from the pom file, iterates over each dependency and downloads it to the directory target/local-repository:
package com.hascode.tutorial; import java.io.File; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import org.apache.maven.model.Model; import org.apache.maven.model.building.DefaultModelBuilderFactory; import org.apache.maven.model.building.DefaultModelBuildingRequest; import org.apache.maven.model.building.ModelBuilder; import org.apache.maven.model.building.ModelBuildingResult; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; import org.eclipse.aether.impl.DefaultServiceLocator; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; import org.eclipse.aether.spi.connector.transport.TransporterFactory; import org.eclipse.aether.transport.file.FileTransporterFactory; import org.eclipse.aether.transport.http.HttpTransporterFactory; public class DownloadingArtifactsByPomExample { public static final String TARGET_LOCAL_REPOSITORY = "target/local-repository"; public static void main(String[] args) throws Exception { File projectPomFile = Paths.get("", "pom.xml").toAbsolutePath().toFile(); System.out.printf("loading this sample project's Maven descriptor from %s\n", projectPomFile); System.out.printf("local Maven repository set to %s\n", Paths.get("", TARGET_LOCAL_REPOSITORY).toAbsolutePath()); RepositorySystem repositorySystem = getRepositorySystem(); RepositorySystemSession repositorySystemSession = getRepositorySystemSession(repositorySystem); final DefaultModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest() .setPomFile(projectPomFile); ModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); ModelBuildingResult modelBuildingResult = modelBuilder.build(modelBuildingRequest); Model model = modelBuildingResult.getEffectiveModel(); System.out.printf("Maven model resolved: %s, parsing its dependencies..\n", model); model.getDependencies().forEach(d -/gt; { System.out.printf("processing dependency: %s\n", d); Artifact artifact = new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getType(), d.getVersion()); ArtifactRequest artifactRequest = new ArtifactRequest(); artifactRequest.setArtifact(artifact); artifactRequest.setRepositories(getRepositories(repositorySystem, repositorySystemSession)); try { ArtifactResult artifactResult = repositorySystem .resolveArtifact(repositorySystemSession, artifactRequest); artifact = artifactResult.getArtifact(); System.out.printf("artifact %s resolved to %s\n", artifact, artifact.getFile()); } catch (ArtifactResolutionException e) { System.err.printf("error resolving artifact: %s\n", e.getMessage()); } }); } public static RepositorySystem getRepositorySystem() { DefaultServiceLocator serviceLocator = MavenRepositorySystemUtils.newServiceLocator(); serviceLocator .addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); serviceLocator.addService(TransporterFactory.class, FileTransporterFactory.class); serviceLocator.addService(TransporterFactory.class, HttpTransporterFactory.class); serviceLocator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() { @Override public void serviceCreationFailed(Class<?/gt; type, Class<?/gt; impl, Throwable exception) { System.err.printf("error creating service: %s\n", exception.getMessage()); exception.printStackTrace(); } }); return serviceLocator.getService(RepositorySystem.class); } public static DefaultRepositorySystemSession getRepositorySystemSession(RepositorySystem system) { DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils .newSession(); LocalRepository localRepository = new LocalRepository(TARGET_LOCAL_REPOSITORY); repositorySystemSession.setLocalRepositoryManager( system.newLocalRepositoryManager(repositorySystemSession, localRepository)); repositorySystemSession.setRepositoryListener(new ConsoleRepositoryEventListener()); return repositorySystemSession; } public static List<RemoteRepository/gt; getRepositories(RepositorySystem system, RepositorySystemSession session) { return Arrays.asList(getCentralMavenRepository()); } private static RemoteRepository getCentralMavenRepository() { return new RemoteRepository.Builder("central", "default", "http://central.maven.org/maven2/") .build(); } }
Status Event Listener
To receive some information what our application is doing, we’re adding the following listener for repository events:
package com.hascode.tutorial; import org.eclipse.aether.AbstractRepositoryListener; import org.eclipse.aether.RepositoryEvent; public class ConsoleRepositoryEventListener extends AbstractRepositoryListener { @Override public void artifactInstalled(RepositoryEvent event) { System.out.printf("artifact %s installed to file %s\n", event.getArtifact(), event.getFile()); } @Override public void artifactInstalling(RepositoryEvent event) { System.out.printf("installing artifact %s to file %s\n", event.getArtifact(), event.getFile()); } @Override public void artifactResolved(RepositoryEvent event) { System.out.printf("artifact %s resolved from repository %s\n", event.getArtifact(), event.getRepository()); } @Override public void artifactDownloading(RepositoryEvent event) { System.out.printf("downloading artifact %s from repository %s\n", event.getArtifact(), event.getRepository()); } @Override public void artifactDownloaded(RepositoryEvent event) { System.out.printf("downloaded artifact %s from repository %s\n", event.getArtifact(), event.getRepository()); } @Override public void artifactResolving(RepositoryEvent event) { System.out.printf("resolving artifact %s\n", event.getArtifact()); } }
Running the Example
We may now run our application in our IDE of choice or using Maven in the command-line:
$ mvn clean compile exec:java -Dexec.mainClass=com.hascode.tutorial.DownloadingArtifactsByPomExample Maven model resolved: com.hascode.tutorial:eclipse-aether-sample:jar:1.0.0, parsing its dependencies.. processing dependency: Dependency {groupId=org.eclipse.aether, artifactId=aether-api, version=1.1.0, type=jar} resolving artifact org.eclipse.aether:aether-api:jar:1.1.0 downloading artifact org.eclipse.aether:aether-api:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) downloaded artifact org.eclipse.aether:aether-api:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-api:jar:1.1.0 resolved from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-api:jar:1.1.0 resolved to /data/project/eclipse-aether-sample/target/local-repository/org/eclipse/aether/aether-api/1.1.0/aether-api-1.1.0.jar processing dependency: Dependency {groupId=org.eclipse.aether, artifactId=aether-spi, version=1.1.0, type=jar} resolving artifact org.eclipse.aether:aether-spi:jar:1.1.0 downloading artifact org.eclipse.aether:aether-spi:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) downloaded artifact org.eclipse.aether:aether-spi:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-spi:jar:1.1.0 resolved from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-spi:jar:1.1.0 resolved to /data/project/eclipse-aether-sample/target/local-repository/org/eclipse/aether/aether-spi/1.1.0/aether-spi-1.1.0.jar processing dependency: Dependency {groupId=org.eclipse.aether, artifactId=aether-util, version=1.1.0, type=jar} resolving artifact org.eclipse.aether:aether-util:jar:1.1.0 downloading artifact org.eclipse.aether:aether-util:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) downloaded artifact org.eclipse.aether:aether-util:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-util:jar:1.1.0 resolved from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-util:jar:1.1.0 resolved to /data/project/eclipse-aether-sample/target/local-repository/org/eclipse/aether/aether-util/1.1.0/aether-util-1.1.0.jar processing dependency: Dependency {groupId=org.eclipse.aether, artifactId=aether-impl, version=1.1.0, type=jar} resolving artifact org.eclipse.aether:aether-impl:jar:1.1.0 downloading artifact org.eclipse.aether:aether-impl:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) downloaded artifact org.eclipse.aether:aether-impl:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-impl:jar:1.1.0 resolved from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-impl:jar:1.1.0 resolved to /data/project/eclipse-aether-sample/target/local-repository/org/eclipse/aether/aether-impl/1.1.0/aether-impl-1.1.0.jar processing dependency: Dependency {groupId=org.eclipse.aether, artifactId=aether-connector-basic, version=1.1.0, type=jar} resolving artifact org.eclipse.aether:aether-connector-basic:jar:1.1.0 downloading artifact org.eclipse.aether:aether-connector-basic:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) downloaded artifact org.eclipse.aether:aether-connector-basic:jar:1.1.0 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-connector-basic:jar:1.1.0 resolved from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.eclipse.aether:aether-connector-basic:jar:1.1.0 resolved to /data/project/eclipse-aether-sample/target/local-repository/org/eclipse/aether/aether-connector-basic/1.1.0/aether-connector-basic-1.1.0.jar [..] processing dependency: Dependency {groupId=org.apache.maven, artifactId=maven-aether-provider, version=3.2.1, type=jar} resolving artifact org.apache.maven:maven-aether-provider:jar:3.2.1 downloading artifact org.apache.maven:maven-aether-provider:jar:3.2.1 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) downloaded artifact org.apache.maven:maven-aether-provider:jar:3.2.1 from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.apache.maven:maven-aether-provider:jar:3.2.1 resolved from repository central (http://central.maven.org/maven2/, default, releases+snapshots) artifact org.apache.maven:maven-aether-provider:jar:3.2.1 resolved to /data/project/eclipse-aether-sample/target/local-repository/org/apache/maven/maven-aether-provider/3.2.1/maven-aether-provider-3.2.1.jar
Download Result
Looking into our local directory target/local-repository we see that all dependencies were downloaded as expected:
$ tree target/local-repository target/local-repository └── org ├── apache │ └── maven │ └── maven-aether-provider │ └── 3.2.1 │ ├── maven-aether-provider-3.2.1.jar │ ├── maven-aether-provider-3.2.1.jar.sha1 │ └── _remote.repositories └── eclipse └── aether ├── aether-api │ └── 1.1.0 │ ├── aether-api-1.1.0.jar │ ├── aether-api-1.1.0.jar.sha1 │ └── _remote.repositories ├── aether-connector-basic │ └── 1.1.0 │ ├── aether-connector-basic-1.1.0.jar │ ├── aether-connector-basic-1.1.0.jar.sha1 │ └── _remote.repositories ├── aether-impl │ └── 1.1.0 │ ├── aether-impl-1.1.0.jar │ ├── aether-impl-1.1.0.jar.sha1 │ └── _remote.repositories ├── aether-spi │ └── 1.1.0 │ ├── aether-spi-1.1.0.jar │ ├── aether-spi-1.1.0.jar.sha1 │ └── _remote.repositories ├── aether-transport-file │ └── 1.1.0 │ ├── aether-transport-file-1.1.0.jar │ ├── aether-transport-file-1.1.0.jar.sha1 │ └── _remote.repositories ├── aether-transport-http │ └── 1.1.0 │ ├── aether-transport-http-1.1.0.jar │ ├── aether-transport-http-1.1.0.jar.sha1 │ └── _remote.repositories └── aether-util └── 1.1.0 ├── aether-util-1.1.0.jar ├── aether-util-1.1.0.jar.sha1 └── _remote.repositories 21 directories, 24 files
Tutorial Sources
Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/eclipse-aether-example.git
Resources
Tags: aether, eclipse, maven, pom, reactor, repository, resolve
September 9th, 2017 at 7:10 pm
Alternatively, you can use Shrinkwrap Resolver, which makes it really simple.
Maven.resolver().resolve(“G:A:V”).withoutTransitivity().asSingleFile();
Give it a spin once :) https://github.com/shrinkwrap/resolver
September 9th, 2017 at 8:49 pm
Hi Bartosz,
thanks for your remarks – Shrinkwrap Resolver looks really nice indeed – I’ll definitely try it out :)
Which artifact do I need to add to get a reference to the “Maven” class in your example?
March 7th, 2018 at 3:07 pm
Thanks for your examples for downloading artifacts from the maven repository.
Do you have any example to upload artifacts to the repositories.
July 20th, 2018 at 4:33 pm
Hi Micha,
Very nice article thanks. I’ve tried it using Maven Resolver v1.1.1. I got an issue with this line:
final DefaultModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest()
.setPomFile(projectPomFile);
I’m getting:
java.lang.NullPointerException: request.modelResolver cannot be null (parent POM org.xwiki.platform:xwiki-platform-test:10.6-SNAPSHOT and POM org.xwiki.platform:xwiki-platform-test-war:[unknown-version] (/Users/vmassol/dev/xwiki/xwiki-platform/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-war/./pom.xml))
at org.apache.commons.lang3.Validate.notNull(Validate.java:225)
at org.apache.maven.model.building.DefaultModelBuilder.readParentExternally(DefaultModelBuilder.java:994)
I think it’s missing setting the ModelResolver, as in:
DefaultModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest()
.setPomFile(new File(“./pom.xml”))
.setModelResolver(…. what do we put here?…):
Any idea?
Thanks
July 25th, 2018 at 9:18 pm
Hi Vincent,
you could possibly use the DefaultModelResolver but I thought that this one would be used anyway if nothing else is specified (like the tutorial’s examples).
Please post an update if it worked for you or provide your pom.xml file that produces the error so I can try to reproduce the problem.
Cheers,
Micha