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.

using eclipse aether
Figure 1. Using Eclipse Aether.

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
using eclipse aether
Figure 2. Running in IntelliJ

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