5

Short: Given (groupId, artifactId, version, repository URL) can I programmatically have Maven resolve the artifact URL?

Long: Given (groupId, artifactId, version, repository URL), one can download a Maven artifact. Usually the URL to the artifact looks like:

scheme://{repository}/{groupId}/{artifactId}/{version}/{artifactId}-{version}.jar

For (org.apache.maven, maven-core, 3.0.0, http://repo.maven.org/maven2), the artifact URL resolves to:

http://repo.maven.org/maven2/org/apache/maven/maven-core/3.0.0/maven-core-3.0.0.jar

Using the inferred pattern above, I can use string concatenations to produce the artifact URL for any given (groupId, artifactId, version, repository URL). However, I do not want to rely on this inferred pattern since it is subject to change in future versions of Maven.

Can I programmatically have Maven resolve the artifact URL for me given (groupId, artifactId, version, repository URL)?

1
  • Doing this so it works in every scenario is not at all trivial. Plugins, BOMs jar/aar differences (if using Android), can all make this difficult. When we faced the issue, we ended up making a Gradle script, defining a custom repository, and having a proxy to capture all URLs as they were resolved. Commented Jul 23, 2023 at 10:26

3 Answers 3

2

Many Maven repositories have an API that you can use to search for artifacts. For example, the central Maven repository has one, for which the documentation is here.

For your example, the API of the central repository serves the following URL for linking directly to a search:

http://search.maven.org/#search|ga|1|g:"org.apache.maven" AND a:"maven-core" AND v:"3.0"

And the following URL for returning XML data (or JSON, by changing the last parameter):

http://search.maven.org/solrsearch/select?q=g:"org.apache.maven" AND a:"maven-core" AND v:"3.0"&wt=xml

In this case, you still have to create a URL for the JAR manually (or scrape the search results for it), but other repositories (like Nexus) provide this in the API results directly.

Sign up to request clarification or add additional context in comments.

1 Comment

This is perfect for me. The docs for nexus are help.sonatype.com/repomanager3/rest-and-integration-api/…
1

Something like ivy would be more easily embedded in the manner you are suggesting. Ivy can use the same layout as maven, or a custom layout.

1 Comment

Ivy is a good way to embed the resolving of Maven artifacts in an application. It is also possible to embed Maven itself, but the code to drive Maven handling the downloads ends up much more complicated than Ivy. I tried both approaches and stuck with Ivy in the end.
1
+500

Please, consider running the following command:

mvn -X -U compile

using this test pom.xml (note that the provided Jackson version not - yet - exists):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mvn-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>20.14.2</version>
        </dependency>

    </dependencies>

</project>

Please, note that among information Maven will provide the following logs:

[DEBUG] =======================================================================
[DEBUG] Using transporter WagonTransporter with priority -1.0 for https://repo.maven.apache.org/maven2
[DEBUG] Using connector BasicRepositoryConnector with priority 0.0 for https://repo.maven.apache.org/maven2
Downloading from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/20.14.2/jackson-databind-20.14.2.pom
[DEBUG] Writing tracking file /Users/jccampanero/.m2/repository/com/fasterxml/jackson/core/jackson-databind/20.14.2/jackson-databind-20.14.2.pom.lastUpdated
...

and this error trace:

Caused by: org.eclipse.aether.transfer.ArtifactNotFoundException: Could not find artifact com.fasterxml.jackson.core:jackson-databind:jar:20.14.2 in central (https://repo.maven.apache.org/maven2)
    at org.eclipse.aether.connector.basic.ArtifactTransportListener.transferFailed (ArtifactTransportListener.java:48)
    at org.eclipse.aether.connector.basic.BasicRepositoryConnector$TaskRunner.run (BasicRepositoryConnector.java:369)
    at org.eclipse.aether.util.concurrency.RunnableErrorForwarder$1.run (RunnableErrorForwarder.java:75)
    at org.eclipse.aether.connector.basic.BasicRepositoryConnector$DirectExecutor.execute (BasicRepositoryConnector.java:628)
    at org.eclipse.aether.connector.basic.BasicRepositoryConnector.get (BasicRepositoryConnector.java:262)
    at org.eclipse.aether.internal.impl.DefaultArtifactResolver.performDownloads (DefaultArtifactResolver.java:513)
    at org.eclipse.aether.internal.impl.DefaultArtifactResolver.resolve (DefaultArtifactResolver.java:401)
    at org.eclipse.aether.internal.impl.DefaultArtifactResolver.resolveArtifacts (DefaultArtifactResolver.java:229)
    at org.eclipse.aether.internal.impl.DefaultRepositorySystem.resolveDependencies (DefaultRepositorySystem.java:340)

As you can see, by default Maven uses BasicRepositoryConnector and Wagon to download the requested artifacts.

The actual download is performed in the get method: please, note it within the provided error trace.

For the actual download, among other things, it will use a RepositoryLayout provided by the configured RepositoryLayoutFactory by default, see here and here, Maven2RepositoryLayoutFactory.

Please, consider review the implemented getLocation methods for the default Maven2RepositoryLayoutFactory, written partially here for completeness:

public URI getLocation(Artifact artifact, boolean upload) {
    StringBuilder path = new StringBuilder(128);


    path.append(artifact.getGroupId().replace('.', '/')).append('/');


    path.append(artifact.getArtifactId()).append('/');


    path.append(artifact.getBaseVersion()).append('/');


    path.append(artifact.getArtifactId()).append('-').append(artifact.getVersion());


    if (artifact.getClassifier().length() > 0) {
        path.append('-').append(artifact.getClassifier());
    }


    if (artifact.getExtension().length() > 0) {
        path.append('.').append(artifact.getExtension());
    }


    return toUri(path.toString());
}

As you can see it matches exactly the path of the URL used for the actual download:

https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/20.14.2/jackson-databind-20.14.2.jar

You can include new repository layouts if required although I never did myself: I came across some blogs like this one that could be of help.

I am aware that the repository layout is typically conceived for local directory structure, but as described in the RepositoryLayoutFactory interface javadoc:

/**
 * A factory to obtain repository layouts. A repository layout is responsible to map an artifact or some metadata to a
 * URI relative to the repository root where the resource resides. When the repository system needs to access a given
 * remote repository, it iterates the registered factories in descending order of their priority and calls
 * {@link #newInstance(RepositorySystemSession, RemoteRepository)} on them. The first layout returned by a factory will
 * then be used for transferring artifacts/metadata.
 */

And the description of the getLocation methods in RepositoryLayout:

/**
 * Gets the location within a remote repository where the specified artifact resides. The URI is relative to the
 * root directory of the repository.
 *
 * @param artifact The artifact to get the URI for, must not be {@code null}.
 * @param upload   {@code false} if the artifact is being downloaded, {@code true} if the artifact is being
 *                 uploaded.
 * @return The relative URI to the artifact, never {@code null}.
 */
URI getLocation(Artifact artifact, boolean upload);


/**
 * Gets the location within a remote repository where the specified metadata resides. The URI is relative to the
 * root directory of the repository.
 *
 * @param metadata The metadata to get the URI for, must not be {@code null}.
 * @param upload   {@code false} if the metadata is being downloaded, {@code true} if the metadata is being
 *                 uploaded.
 * @return The relative URI to the metadata, never {@code null}.
 */
URI getLocation(Metadata metadata, boolean upload);

That means that in order to resolve the URL of a certain artifact there is not a single answer, it will depend on the Maven Repository Layout provided.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.