Downloading Maven Artifacts from a POM file programmatically with Eclipse Aether

September 8th, 2017 by

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.

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.

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 Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/eclipse-aether-example.git

Resources

Tags: , , , , , ,

5 Responses to “Downloading Maven Artifacts from a POM file programmatically with Eclipse Aether”

  1. Bartosz Says:

    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

  2. Micha Kops Says:

    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?

  3. Subhakar Kotta Says:

    Thanks for your examples for downloading artifacts from the maven repository.
    Do you have any example to upload artifacts to the repositories.

  4. Vincent Says:

    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

  5. Micha Kops Says:

    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

Search
Categories