Sometimes 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.
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 GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/eclipse-aether-example.git