Maven and the Project Formerly Known As Æther

I am hacking and exploring my ideas around CDI and linking and part of whatever that might become will be Maven artifact resolution.

If, like me, you’ve been working with Maven since the 1.0 days (!), you may be amused by the long, tortured path the dependency resolution machine at its core has taken over the years.

First, there was Maven artifact resolution baked into the core.

Then Jason decided that it might be better if this were broken off into its own project.  So it became Sonatype Æther and rapidly grew to incorporate every feature under the sun except an email client.  It got transferred to Sonatype which was the company he was running at the time.

Then people got very enamored of Eclipse in general, and so Æther, without any changes, got moved to the Eclipse Foundation.  Then the package names changed and a million trillion confused developers tried to figure things out on StackOverflow.

Then it turned out that no one other than Maven and maybe some of Jason’s companies’ work was using Æther in either Sonatype or Eclipse form, so Eclipse Æther has just recently been—wait for it—folded back into Maven.  Of course, the Eclipse Æther page doesn’t (seem to?) mention this, and if you try to go to its documentation page then at least as of this writing you get a 500 error.  If you hunt a little bit harder, you find another related page that finally tells you the whole thing has been archived.

So anytime you see the word Æther you should now think Maven Artifact Resolver.

But make sure that you don’t therefore think that the name of the actual Maven artifact representing this project is maven-artifact-resolver, because that is the old artifact embodying the 2009 version of Maven’s dependency resolution code!  The new artifact name for the Maven Artifact Resolver project is simply maven-resolver.  Still with me?

Fine. So you’ve navigated all this and now you want to work with Maven Artifact Resolver (maven-resolver), the project formerly known as Some Kind of Æther.

If, like me, you want to use the machinery outside of Maven proper, then you are probably conditioned, like me, to look for some sort of API artifact. Sure enough, there is one.  (Note that for all the history I’ve covered above, the package names confusingly still start with org.eclipse.aether.)

Now you need to know what implementation to back this with. This is not as straightforward as it seems. The Maven project page teases you with a few hints, but it turns out that merely using these various other Maven projects probably won’t get you where you want to go, which in most cases is probably a Maven-like experience (reading from .m2/settings.xml files, being able to work with local repositories, etc. etc.).  (Recall that the project formerly known as Some Kind of Æther and now known as Maven Artifact Resolver expanded to become very, very flexible at the expense of being simple to use, so it can read from repositories that aren’t just Maven repositories, so it inherently doesn’t know anything about Maven, even though now it has been folded back into the Maven codebase.)

Fortunately, in the history of all this, there was a class called MavenRepositorySystemUtils.  If you search for this, you’ll find its javadocs, but these are not the javadocs you’re looking for.  Let’s pretend for a moment they were: you would, if you used this class, be able to get a RepositorySystem rather easily:

final ServiceLocator serviceLocator = MavenRepositorySystemUtils.newServiceLocator();
assert serviceLocator != null;
final RepositorySystem repositorySystem = serviceLocator.getService(RepositorySystem.class);
assert repositorySystem != null;

But these javadocs are for a class that uses Eclipse Æther, so no soup for you.

So now what?  It turns out that that class still exists and has been refactored to use Maven Artifact Resolver (maven-resolver), but its project page and javadocs and whatnot don’t exist (yet?).  The artifact you’re looking for, then, turns out to be maven-resolver-provider, and as of this writing exists only in its 3.5.0-alpha-1 version.

So if you make sure that artifact is available then you’ll be able to use the Maven Artifact Resolver APIs to interact with Maven repositories and download and resolve dependencies.

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Repository;
import org.apache.maven.settings.Settings;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.building.DefaultSettingsBuilder;
import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingResult;
import org.apache.maven.settings.building.SettingsBuilder;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.apache.maven.settings.building.SettingsProblem;
import org.eclipse.aether.DefaultRepositoryCache;
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.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.impl.DefaultServiceLocator.ErrorHandler;
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RemoteRepository.Builder;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.TransferEvent;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class TestMavenResolverUsage {
public TestMavenResolverUsage() {
public void testEverything() throws Exception {
// See
// et al. for general (undocumented) recipe.
final 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 ErrorHandler() {
public final void serviceCreationFailed(final Class<?> type, final Class<?> impl, final Throwable exception) {
if (exception != null) {
final RepositorySystem repositorySystem = serviceLocator.getService(RepositorySystem.class);
final Settings settings = getSettings();
final DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils.newSession();
repositorySystemSession.setTransferListener(new TransferListener());
repositorySystemSession.setCache(new DefaultRepositoryCache());
final Collection<? extends Mirror> mirrors = settings.getMirrors();
if (mirrors != null && !mirrors.isEmpty()) {
final DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
for (final Mirror mirror : mirrors) {
assert mirror != null;
false, /* not a repository manager; settings.xml does not encode this information */
String localRepositoryString = settings.getLocalRepository();
if (localRepositoryString == null) {
localRepositoryString = System.getProperty("user.home") + "/.m2/repository";
final LocalRepository localRepository = new LocalRepository(localRepositoryString);
final LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(repositorySystemSession, localRepository);
List<RemoteRepository> remoteRepositories = new ArrayList<>();
final Map<String, Profile> profiles = settings.getProfilesAsMap();
if (profiles != null && !profiles.isEmpty()) {
final Collection<String> activeProfileKeys = settings.getActiveProfiles();
if (activeProfileKeys != null && !activeProfileKeys.isEmpty()) {
for (final String activeProfileKey : activeProfileKeys) {
final Profile activeProfile = profiles.get(activeProfileKey);
if (activeProfile != null) {
final Collection<Repository> repositories = activeProfile.getRepositories();
if (repositories != null && !repositories.isEmpty()) {
for (final Repository repository : repositories) {
if (repository != null) {
Builder builder = new Builder(repository.getId(), repository.getLayout(), repository.getUrl());
final org.apache.maven.settings.RepositoryPolicy settingsReleasePolicy = repository.getReleases();
if (settingsReleasePolicy != null) {
final org.eclipse.aether.repository.RepositoryPolicy releasePolicy = new org.eclipse.aether.repository.RepositoryPolicy(settingsReleasePolicy.isEnabled(), settingsReleasePolicy.getUpdatePolicy(), settingsReleasePolicy.getChecksumPolicy());
builder = builder.setReleasePolicy(releasePolicy);
final org.apache.maven.settings.RepositoryPolicy settingsSnapshotPolicy = repository.getSnapshots();
if (settingsSnapshotPolicy != null) {
final org.eclipse.aether.repository.RepositoryPolicy snapshotPolicy = new org.eclipse.aether.repository.RepositoryPolicy(settingsSnapshotPolicy.isEnabled(), settingsSnapshotPolicy.getUpdatePolicy(), settingsSnapshotPolicy.getChecksumPolicy());
builder = builder.setSnapshotPolicy(snapshotPolicy);
final RemoteRepository remoteRepository =;
assert remoteRepository != null;
final RemoteRepository mavenCentral = new Builder("central", "default", ";).build();
assert mavenCentral != null;
remoteRepositories = repositorySystem.newResolutionRepositories(repositorySystemSession, remoteRepositories);
final Artifact artifact = new DefaultArtifact("org.microbean", "microbean-configuration-cdi", "jar", "0.1.0");
final DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE);
final CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(new Dependency(artifact, JavaScopes.COMPILE));
// collectRequest.setRepositories(Collections.singletonList(mavenCentral));
final DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, classpathFilter);
final DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySystemSession, dependencyRequest);
final List<ArtifactResult> artifactResults = dependencyResult.getArtifactResults();
public static final Settings getSettings() throws SettingsBuildingException {
final SettingsBuilder settingsBuilder = new DefaultSettingsBuilderFactory().newInstance(); // this method should be static!
assert settingsBuilder != null;
final DefaultSettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
// settingsBuildingRequest.setUserProperties(userProperties); // TODO: implement this
settingsBuildingRequest.setGlobalSettingsFile(new File("/usr/local/maven/conf/settings.xml")); // TODO: do this for real
settingsBuildingRequest.setUserSettingsFile(new File(new File(System.getProperty("user.home")), ".m2/settings.xml"));
final SettingsBuildingResult settingsBuildingResult =;
assert settingsBuildingResult != null;
final List<SettingsProblem> settingsBuildingProblems = settingsBuildingResult.getProblems();
if (settingsBuildingProblems != null && !settingsBuildingProblems.isEmpty()) {
throw new SettingsBuildingException(settingsBuildingProblems);
return settingsBuildingResult.getEffectiveSettings();
private static final class TransferListener extends AbstractTransferListener {
private TransferListener() {
public void transferInitiated(final TransferEvent event) {
System.out.println("*** transfer initiated: " + event);
public void transferStarted(final TransferEvent event) {
System.out.println("*** transfer started: " + event);
public void transferProgressed(final TransferEvent event) {
System.out.println("*** transfer progressed: " + event);
public void transferSucceeded(final TransferEvent event) {
System.out.println("*** transfer succeeded: " + event);
public void transferCorrupted(final TransferEvent event) {
System.out.println("*** transfer corrupted: " + event);
public void transferFailed(final TransferEvent event) {
System.out.println("*** transfer failed: " + event);


Author: Laird Nelson

Devoted husband and father; working on Helidon at the intersection of Java, Jakarta EE, architecture, Kubernetes and microservices at Oracle; open source guy; Hammond B3 player and Bainbridge Islander.

6 thoughts on “Maven and the Project Formerly Known As Æther”

  1. WTF?!??!? Seriously. I am trying to update an old company plugin from maven 2 to 3 and it has been a nightmare. Thanks so much for clarifying this mess and for the code examples!

Comments are closed.

%d bloggers like this: