This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 9780ca1baf [MNG-8015] Control the type of path where each dependency 
can be placed (#1401)
9780ca1baf is described below

commit 9780ca1baff15e977d41da4687abc7e4f355bd77
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Fri Mar 1 18:18:13 2024 +0100

    [MNG-8015] Control the type of path where each dependency can be placed 
(#1401)
    
    Co-authored-by: Martin Desruisseaux <martin.desruisse...@geomatys.com>
---
 .../java/org/apache/maven/api/ExtensibleEnum.java  |   2 +
 .../java/org/apache/maven/api/JavaPathType.java    | 319 +++++++++++++++++++
 .../main/java/org/apache/maven/api/PathType.java   | 131 ++++++++
 .../main/java/org/apache/maven/api/Session.java    | 242 ++++++++++++--
 .../src/main/java/org/apache/maven/api/Type.java   |  80 ++++-
 .../api/services/DependencyResolverRequest.java    |  63 +++-
 .../api/services/DependencyResolverResult.java     |  46 +++
 .../maven/artifact/handler/ArtifactHandler.java    |  11 +-
 .../artifact/handler/ArtifactHandlerMock.java      |   2 +
 .../maven/repository/TestArtifactHandler.java      |   1 +
 .../artifact/handler/DefaultArtifactHandler.java   |   3 +
 .../manager/DefaultArtifactHandlerManager.java     |   4 +-
 .../maven/internal/aether/TypeRegistryAdapter.java |   5 +-
 .../maven/internal/impl/AbstractSession.java       |  32 +-
 .../maven/internal/impl/DefaultDependency.java     |   1 +
 .../internal/impl/DefaultDependencyResolver.java   | 134 +++-----
 .../impl/DefaultDependencyResolverResult.java      | 348 +++++++++++++++++++++
 .../maven/internal/impl/DefaultTypeRegistry.java   |   6 +-
 .../maven/internal/impl/PathModularization.java    | 265 ++++++++++++++++
 .../internal/impl/PathModularizationCache.java     | 134 ++++++++
 .../impl/TransformedArtifactHandler.java           |   1 +
 .../org/apache/maven/project/MavenProject.java     | 149 +++++----
 .../maven/project/artifact/PluginArtifact.java     |   1 +
 .../maven/project/artifact/ProjectArtifact.java    |   1 +
 .../org/apache/maven/internal/impl/TestApi.java    |  67 ++--
 .../maven/internal/impl/TestArtifactHandler.java   |   1 +
 .../internal/ArtifactDescriptorReaderDelegate.java |   2 +-
 .../repository/internal/type/DefaultType.java      |  26 +-
 .../internal/type/DefaultTypeProvider.java         |  40 ++-
 29 files changed, 1857 insertions(+), 260 deletions(-)

diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java
index fc1412a36a..4b8b74ca0b 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java
@@ -22,6 +22,8 @@ import org.apache.maven.api.annotations.Experimental;
 import org.apache.maven.api.annotations.Nonnull;
 
 /**
+ * Interface that defines some kind of enums that can be extended by Maven 
plugins or extensions.
+ *
  * Implementation must have {@code equals()} and {@code hashCode()} 
implemented, so implementations of this interface
  * can be used as keys.
  *
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java
new file mode 100644
index 0000000000..7f7b2a22e4
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.api;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+
+/**
+ * The option of a Java command-line tool where to place the paths to some 
dependencies.
+ * A {@code PathType} can identify the class-path, the module-path, the 
patches for a specific module,
+ * or another kind of path.
+ *
+ * <p>One path type is handled in a special way: unlike other options,
+ * the paths specified in a {@code --patch-module} Java option is effective 
only for a specified module.
+ * This type is created by calls to {@link #patchModule(String)} and a new 
instance must be created for
+ * every module to patch.</p>
+ *
+ * <p>Path types are often exclusive. For example, a dependency should not be 
both on the Java class-path
+ * and on the Java module-path.</p>
+ *
+ * @see 
org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths()
+ *
+ * @since 4.0.0
+ */
+@Experimental
+public enum JavaPathType implements PathType {
+    /**
+     * The path identified by the Java {@code --class-path} option.
+     * Used for compilation, execution and Javadoc among others.
+     *
+     * <p><b>Context-sensitive interpretation:</b>
+     * A dependency with this path type will not necessarily be placed on the 
class-path.
+     * There are two circumstances where the dependency may nevertheless be 
placed somewhere else:
+     * </p>
+     * <ul>
+     *   <li>If {@link #MODULES} path type is also set, then the dependency 
can be placed either on the
+     *       class-path or on the module-path, but only one of those. The 
choice is up to the plugin,
+     *       possibly using heuristic rules (Maven 3 behavior).</li>
+     *   <li>If a {@link #patchModule(String)} is also set and the main JAR 
file is placed on the module-path,
+     *       then the test dependency will be placed on the Java {@code 
--patch-module} option instead of the
+     *       class-path.</li>
+     * </ul>
+     */
+    CLASSES("--class-path"),
+
+    /**
+     * The path identified by the Java {@code --module-path} option.
+     * Used for compilation, execution and Javadoc among others.
+     *
+     * <p><b>Context-sensitive interpretation:</b>
+     * A dependency with this flag will not necessarily be placed on the 
module-path.
+     * There are two circumstances where the dependency may nevertheless be 
placed somewhere else:
+     * </p>
+     * <ul>
+     *   <li>If {@link #CLASSES} path type is also set, then the dependency 
<em>should</em> be placed on the
+     *       module-path, but is also compatible with placement on the 
class-path. Compatibility can
+     *       be achieved, for example, by repeating in the {@code 
META-INF/services/} directory the services
+     *       that are declared in the {@code module-info.class} file. In that 
case, the path type can be chosen
+     *       by the plugin.</li>
+     *   <li>If a {@link #patchModule(String)} is also set and the main JAR 
file is placed on the module-path,
+     *       then the test dependency will be placed on the Java {@code 
--patch-module} option instead of the
+     *       {@code --module-path} option.</li>
+     * </ul>
+     */
+    MODULES("--module-path"),
+
+    /**
+     * The path identified by the Java {@code --upgrade-module-path} option.
+     */
+    UPGRADE_MODULES("--upgrade-module-path"),
+
+    /**
+     * The path identified by the Java {@code --patch-module} option.
+     * Note that this option is incomplete, because it must be followed by a 
module name.
+     * Use this type only when the module to patch is unknown.
+     *
+     * @see #patchModule(String)
+     */
+    PATCH_MODULE("--patch-module"),
+
+    /**
+     * The path identified by the Java {@code --processor-path} option.
+     */
+    PROCESSOR_CLASSES("--processor-path"),
+
+    /**
+     * The path identified by the Java {@code --processor-module-path} option.
+     */
+    PROCESSOR_MODULES("--processor-module-path"),
+
+    /**
+     * The path identified by the Java {@code -agentpath} option.
+     */
+    AGENT("-agentpath"),
+
+    /**
+     * The path identified by the Javadoc {@code -doclet} option.
+     */
+    DOCLET("-doclet"),
+
+    /**
+     * The path identified by the Javadoc {@code -tagletpath} option.
+     */
+    TAGLETS("-tagletpath");
+
+    /**
+     * Creates a path identified by the Java {@code --patch-module} option.
+     * Contrarily to the other types of paths, this path is applied to only
+     * one specific module. Used for compilation and execution among others.
+     *
+     * <p><b>Context-sensitive interpretation:</b>
+     * This path type makes sense only when a main module is added on the 
module-path by another dependency.
+     * In no main module is found, the patch dependency may be added on the 
class-path or module-path
+     * depending on whether {@link #CLASSES} or {@link #MODULES} is present.
+     * </p>
+     *
+     * @param moduleName name of the module on which to apply the path
+     * @return an identification of the patch-module path for the given module.
+     *
+     * @see Modular#moduleName()
+     */
+    @Nonnull
+    public static Modular patchModule(@Nonnull String moduleName) {
+        return PATCH_MODULE.new Modular(moduleName);
+    }
+
+    /**
+     * The tools option for this path, or {@code null} if none.
+     *
+     * @see #option()
+     */
+    private final String option;
+
+    /**
+     * Creates a new enumeration value for a path associated to the given tool 
option.
+     *
+     * @param option the Java tools option for this path, or {@code null} if 
none
+     */
+    JavaPathType(String option) {
+        this.option = option;
+    }
+
+    @Override
+    public String id() {
+        return name();
+    }
+
+    /**
+     * Returns the name of the tool option for this path. For example, if this 
path type
+     * is {@link #MODULES}, then this method returns {@code "--module-path"}. 
The option
+     * does not include the {@linkplain Modular#moduleName() module name} on 
which it applies.
+     *
+     * @return the name of the tool option for this path type
+     */
+    @Nonnull
+    @Override
+    public Optional<String> option() {
+        return Optional.ofNullable(option);
+    }
+
+    /**
+     * Returns the option followed by a string representation of the given 
path elements.
+     * For example, if this type is {@link #MODULES}, then the option is 
{@code "--module-path"}
+     * followed by the specified path elements.
+     *
+     * @param paths the path to format as a tool option
+     * @return the option associated to this path type followed by the given 
path elements,
+     *         or an empty string if there is no path element
+     * @throws IllegalStateException if no option is associated to this path 
type
+     */
+    @Nonnull
+    @Override
+    public String option(Iterable<? extends Path> paths) {
+        return format(null, paths);
+    }
+
+    /**
+     * Implementation shared with {@link Modular}.
+     */
+    String format(String moduleName, Iterable<? extends Path> paths) {
+        if (option == null) {
+            throw new IllegalStateException("No option is associated to this 
path type.");
+        }
+        String prefix = (moduleName == null) ? (option + ' ') : (option + ' ' 
+ moduleName + '=');
+        StringJoiner joiner = new StringJoiner(File.pathSeparator, prefix, "");
+        joiner.setEmptyValue("");
+        for (Path p : paths) {
+            joiner.add(p.toString());
+        }
+        return joiner.toString();
+    }
+
+    @Override
+    public String toString() {
+        return "PathType[" + id() + "]";
+    }
+
+    /**
+     * Type of path which is applied to only one specific Java module.
+     * The main case is the Java {@code --patch-module} option.
+     *
+     * @see #PATCH_MODULE
+     * @see #patchModule(String)
+     */
+    public final class Modular implements PathType {
+        /**
+         * Name of the module for which a path is specified.
+         */
+        @Nonnull
+        private final String moduleName;
+
+        /**
+         * Creates a new path type for the specified module.
+         *
+         * @param moduleName name of the module for which a path is specified
+         */
+        private Modular(@Nonnull String moduleName) {
+            this.moduleName = Objects.requireNonNull(moduleName);
+        }
+
+        @Override
+        public String id() {
+            return JavaPathType.this.name() + ":" + moduleName;
+        }
+
+        /**
+         * Returns the type of path without indication about the target module.
+         * This is usually {@link #PATCH_MODULE}.
+         *
+         * @return type of path without indication about the target module
+         */
+        @Nonnull
+        public JavaPathType rawType() {
+            return JavaPathType.this;
+        }
+
+        /**
+         * Returns the name of the tool option for this path, not including 
the module name.
+         *
+         * @return name of the tool option for this path, not including the 
module name
+         */
+        @Nonnull
+        public String name() {
+            return JavaPathType.this.name();
+        }
+
+        /**
+         * Returns the name of the module for which a path is specified
+         *
+         * @return name of the module for which a path is specified
+         */
+        @Nonnull
+        public String moduleName() {
+            return moduleName;
+        }
+
+        /**
+         * Returns the name of the tool option for this path.
+         * The option does not include the {@linkplain #moduleName() module 
name} on which it applies.
+         *
+         * @return the name of the tool option for this path type
+         */
+        @Nonnull
+        @Override
+        public Optional<String> option() {
+            return JavaPathType.this.option();
+        }
+
+        /**
+         * Returns the option followed by a string representation of the given 
path elements.
+         * The path elements are separated by an option-specific or 
platform-specific separator.
+         * If the given {@code paths} argument contains no element, then this 
method returns an empty string.
+         *
+         * @param paths the path to format as a string
+         * @return the option associated to this path type followed by the 
given path elements,
+         *         or an empty string if there is no path element.
+         */
+        @Nonnull
+        @Override
+        public String option(Iterable<? extends Path> paths) {
+            return format(moduleName, paths);
+        }
+
+        /**
+         * Returns the programmatic name of this path type, including the 
module to patch.
+         * For example, if this type was created by {@code 
JavaPathType.patchModule("foo.bar")},
+         * then this method returns {@code "PathType[PATCH_MODULE:foo.bar]")}.
+         *
+         * @return the programmatic name together with the module name on 
which it applies
+         */
+        @Nonnull
+        @Override
+        public String toString() {
+            return "PathType[" + id() + "]";
+        }
+    }
+}
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java
new file mode 100644
index 0000000000..e7f80cf4a9
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.api;
+
+import java.nio.file.Path;
+import java.util.Optional;
+
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+
+/**
+ * The option of a command-line tool where to place the paths to some 
dependencies.
+ * A {@code PathType} can identify the Java class-path, the Java module-path,
+ * or another kind of path for another programming language for example.
+ * Path types are often exclusive. For example, a dependency should not be
+ * both on the Java class-path and on the Java module-path.
+ *
+ * @see 
org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths()
+ *
+ * @since 4.0.0
+ */
+@Experimental
+public interface PathType {
+    /**
+     * The type for all paths that could not be placed in any of the types 
requested by a caller.
+     * This type can appear in the return value of a call to
+     * {@link Session#resolveDependencies resolveDependencies(...)} when at 
least one dependency
+     * cannot be associated to any type specified in the {@code desiredTypes} 
argument.
+     * Plugins can choose to report a warning to users when unresolved paths 
exist.
+     */
+    PathType UNRESOLVED = new PathType() {
+        @Override
+        public String name() {
+            return "UNRESOLVED";
+        }
+
+        @Override
+        public String id() {
+            return "UNRESOLVED";
+        }
+
+        @Override
+        public Optional<String> option() {
+            return Optional.empty();
+        }
+
+        @Override
+        public String option(Iterable<? extends Path> paths) {
+            return "";
+        }
+    };
+
+    /**
+     * Returns the unique name of this path type, including the module to 
patch if any.
+     * For example, if this type is {@link JavaPathType#MODULES}, then this 
method returns {@code "MODULES"}.
+     * But if this type was created by {@code 
JavaPathType.patchModule("foo.bar")}, then this method returns
+     * {@code "PATCH_MODULE:foo.bar"}.
+     *
+     * @return the programmatic name together with the module name on which it 
applies
+     * @see #toString()
+     */
+    @Nonnull
+    String id();
+
+    /**
+     * Returns the name of the tool option for this path. For example, if this 
path type
+     * is {@link JavaPathType#MODULES}, then this method returns {@code 
"--module-path"}.
+     * The option does not include the {@linkplain 
JavaPathType.Modular#moduleName() module name}
+     * on which it applies.
+     *
+     * @return the name of the tool option for this path type
+     */
+    @Nonnull
+    Optional<String> option();
+
+    /**
+     * Returns the option followed by a string representation of the given 
path elements.
+     * The path elements are separated by an option-specific or 
platform-specific separator.
+     * If the given {@code paths} argument contains no element, then this 
method returns an empty string.
+     *
+     * <p><b>Examples:</b>
+     * If {@code paths} is a list containing two elements, {@code path1} and 
{@code path2}, then:
+     * </p>
+     * <ul>
+     *   <li>If this type is {@link JavaPathType#MODULES}, then this method 
returns
+     *       {@code "--module-path path1:path2"} on Unix or {@code 
"--module-path path1;path2"} on Windows.</li>
+     *   <li>If this type was created by {@code 
JavaPathType.patchModule("foo.bar")}, then the method returns
+     *       {@code "--patch-module foo.bar=path1:path2"} on Unix or {@code 
"--patch-module foo.bar=path1;path2"}
+     *       on Windows.</li>
+     * </ul>
+     *
+     * @param paths the path to format as a string
+     * @return the option associated to this path type followed by the given 
path elements,
+     *         or an empty string if there is no path element.
+     */
+    @Nonnull
+    String option(Iterable<? extends Path> paths);
+
+    /**
+     * Returns the name of this path type. For example, if this path type
+     * is {@link JavaPathType#MODULES}, then this method returns {@code 
"MODULES"}.
+     *
+     * @return the programmatic name of this path type
+     */
+    @Nonnull
+    String name();
+
+    /**
+     * Returns a string representation for this extensible enum describing a 
path type.
+     * For example {@code "PathType[PATCH_MODULE:foo.bar]"}.
+     */
+    @Nonnull
+    @Override
+    String toString();
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java
index 3aaa1f1165..8f817f4d0f 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java
@@ -195,13 +195,21 @@ public interface Session {
     /**
      * Shortcut for {@code 
getService(RepositoryFactory.class).createLocal(...)}.
      *
+     * @param path location of the local repository to create
+     * @return cache of artifacts downloaded from a remote repository or built 
locally
+     *
      * @see org.apache.maven.api.services.RepositoryFactory#createLocal(Path)
      */
-    LocalRepository createLocalRepository(Path path);
+    @Nonnull
+    LocalRepository createLocalRepository(@Nonnull Path path);
 
     /**
      * Shortcut for {@code 
getService(RepositoryFactory.class).createRemote(...)}.
      *
+     * @param  id identifier of the remote repository to create
+     * @param  url location of the remote repository
+     * @return remote repository that can be used to download or upload 
artifacts
+     *
      * @see 
org.apache.maven.api.services.RepositoryFactory#createRemote(String, String)
      */
     @Nonnull
@@ -210,48 +218,76 @@ public interface Session {
     /**
      * Shortcut for {@code 
getService(RepositoryFactory.class).createRemote(...)}.
      *
+     * @param repository information needed for establishing connections with 
remote repository
+     * @return remote repository that can be used to download or upload 
artifacts
+     *
      * @see 
org.apache.maven.api.services.RepositoryFactory#createRemote(Repository)
      */
     @Nonnull
     RemoteRepository createRemoteRepository(@Nonnull Repository repository);
 
     /**
+     * Creates a coordinate out of string that is formatted like:
+     * {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}.
+     * <p>
      * Shortcut for {@code getService(ArtifactFactory.class).create(...)}.
      *
-     * @see 
org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, 
String, String, String)
+     * @param coordString the string having "standard" coordinate.
+     * @return coordinate used to point to the artifact
+     *
+     * @see 
org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String)
      */
-    ArtifactCoordinate createArtifactCoordinate(String groupId, String 
artifactId, String version, String extension);
+    @Nonnull
+    ArtifactCoordinate createArtifactCoordinate(@Nonnull String coordString);
 
     /**
-     * Creates a coordinate out of string that is formatted like:
-     * {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}
-     * <p>
      * Shortcut for {@code getService(ArtifactFactory.class).create(...)}.
      *
-     * @param coordString the string having "standard" coordinate.
-     * @return an {@code ArtifactCoordinate}, never {@code null}
-     * @see 
org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String)
+     * @param groupId the group identifier, or {@code null} is unspecified
+     * @param artifactId the artifact identifier, or {@code null} is 
unspecified
+     * @param version the artifact version, or {@code null} is unspecified
+     * @param extension the artifact extension, or {@code null} is unspecified
+     * @return coordinate used to point to the artifact
+     *
+     * @see 
org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, 
String, String, String)
      */
-    ArtifactCoordinate createArtifactCoordinate(String coordString);
+    @Nonnull
+    ArtifactCoordinate createArtifactCoordinate(String groupId, String 
artifactId, String version, String extension);
 
     /**
      * Shortcut for {@code getService(ArtifactFactory.class).create(...)}.
      *
+     * @param groupId the group identifier, or {@code null} is unspecified
+     * @param artifactId the artifact identifier, or {@code null} is 
unspecified
+     * @param version the artifact version, or {@code null} is unspecified
+     * @param classifier the artifact classifier, or {@code null} is 
unspecified
+     * @param extension the artifact extension, or {@code null} is unspecified
+     * @param type the artifact type, or {@code null} is unspecified
+     * @return coordinate used to point to the artifact
+     *
      * @see 
org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, 
String, String, String, String, String)
      */
+    @Nonnull
     ArtifactCoordinate createArtifactCoordinate(
             String groupId, String artifactId, String version, String 
classifier, String extension, String type);
 
     /**
      * Shortcut for {@code getService(ArtifactFactory.class).create(...)}.
      *
+     * @param artifact artifact from which to get coordinates
+     * @return coordinate used to point to the artifact
+     *
      * @see 
org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, 
String, String, String, String, String)
      */
-    ArtifactCoordinate createArtifactCoordinate(Artifact artifact);
+    @Nonnull
+    ArtifactCoordinate createArtifactCoordinate(@Nonnull Artifact artifact);
 
     /**
      * Shortcut for {@code getService(DependencyFactory.class).create(...)}.
      *
+     * @param coordinate artifact coordinate to get as a dependency coordinate
+     * @return dependency coordinate for the given artifact
+     *
      * @see DependencyCoordinateFactory#create(Session, ArtifactCoordinate)
      */
     @Nonnull
@@ -260,6 +296,9 @@ public interface Session {
     /**
      * Shortcut for {@code getService(DependencyFactory.class).create(...)}.
      *
+     * @param dependency dependency for which to get the coordinate
+     * @return coordinate for the given dependency
+     *
      * @see DependencyCoordinateFactory#create(Session, Dependency)
      */
     @Nonnull
@@ -268,85 +307,131 @@ public interface Session {
     /**
      * Shortcut for {@code getService(ArtifactFactory.class).create(...)}.
      *
+     * @param groupId the group identifier, or {@code null} is unspecified
+     * @param artifactId the artifact identifier, or {@code null} is 
unspecified
+     * @param version the artifact version, or {@code null} is unspecified
+     * @param extension the artifact extension, or {@code null} is unspecified
+     * @return artifact with the given coordinates
+     *
      * @see org.apache.maven.api.services.ArtifactFactory#create(Session, 
String, String, String, String)
      */
+    @Nonnull
     Artifact createArtifact(String groupId, String artifactId, String version, 
String extension);
 
     /**
      * Shortcut for {@code getService(ArtifactFactory.class).create(...)}.
      *
+     * @param groupId the group identifier, or {@code null} is unspecified
+     * @param artifactId the artifact identifier, or {@code null} is 
unspecified
+     * @param version the artifact version, or {@code null} is unspecified
+     * @param classifier the artifact classifier, or {@code null} is 
unspecified
+     * @param extension the artifact extension, or {@code null} is unspecified
+     * @param type the artifact type, or {@code null} is unspecified
+     * @return artifact with the given coordinates
+     *
      * @see org.apache.maven.api.services.ArtifactFactory#create(Session, 
String, String, String, String, String, String)
      */
+    @Nonnull
     Artifact createArtifact(
             String groupId, String artifactId, String version, String 
classifier, String extension, String type);
 
     /**
      * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
+     * @param coordinate coordinates of the artifact to resolve
+     * @return requested artifact together with the path to its file
      * @throws org.apache.maven.api.services.ArtifactResolverException if the 
artifact resolution failed
+     *
+     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
      */
-    Map.Entry<Artifact, Path> resolveArtifact(ArtifactCoordinate coordinate);
+    @Nonnull
+    Map.Entry<Artifact, Path> resolveArtifact(@Nonnull ArtifactCoordinate 
coordinate);
 
     /**
      * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
+     * @param coordinates coordinates of all artifacts to resolve
+     * @return requested artifacts together with the paths to their files
      * @throws org.apache.maven.api.services.ArtifactResolverException if the 
artifact resolution failed
+     *
+     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
      */
-    Map<Artifact, Path> resolveArtifacts(ArtifactCoordinate... coordinates);
+    @Nonnull
+    Map<Artifact, Path> resolveArtifacts(@Nonnull ArtifactCoordinate... 
coordinates);
 
     /**
      * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
+     * @param coordinates coordinates of all artifacts to resolve
+     * @return requested artifacts together with the paths to their files
      * @throws org.apache.maven.api.services.ArtifactResolverException if the 
artifact resolution failed
+     *
+     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
      */
-    Map<Artifact, Path> resolveArtifacts(Collection<? extends 
ArtifactCoordinate> coordinates);
+    @Nonnull
+    Map<Artifact, Path> resolveArtifacts(@Nonnull Collection<? extends 
ArtifactCoordinate> coordinates);
 
     /**
      * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
+     * @param artifact the artifact to resolve
+     * @return requested artifact together with the path to its file
      * @throws org.apache.maven.api.services.ArtifactResolverException if the 
artifact resolution failed
+     *
+     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
      */
-    Map.Entry<Artifact, Path> resolveArtifact(Artifact artifact);
+    @Nonnull
+    Map.Entry<Artifact, Path> resolveArtifact(@Nonnull Artifact artifact);
 
     /**
      * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
+     * @param artifacts all artifacts to resolve
+     * @return requested artifacts together with the paths to their files
      * @throws org.apache.maven.api.services.ArtifactResolverException if the 
artifact resolution failed
+     *
+     * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, 
Collection)
      */
-    Map<Artifact, Path> resolveArtifacts(Artifact... artifacts);
+    @Nonnull
+    Map<Artifact, Path> resolveArtifacts(@Nonnull Artifact... artifacts);
 
     /**
      * Shortcut for {@code getService(ArtifactInstaller.class).install(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, 
Collection)
+     * @param artifacts the artifacts to install
      * @throws org.apache.maven.api.services.ArtifactInstallerException if the 
artifacts installation failed
+     *
+     * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, 
Collection)
      */
-    void installArtifacts(Artifact... artifacts);
+    void installArtifacts(@Nonnull Artifact... artifacts);
 
     /**
      * Shortcut for {@code getService(ArtifactInstaller.class).install(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, 
Collection)
+     * @param artifacts the artifacts to install
      * @throws org.apache.maven.api.services.ArtifactInstallerException if the 
artifacts installation failed
+     *
+     * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, 
Collection)
      */
-    void installArtifacts(Collection<Artifact> artifacts);
+    void installArtifacts(@Nonnull Collection<Artifact> artifacts);
 
     /**
      * Shortcut for {@code getService(ArtifactDeployer.class).deploy(...)}.
      *
-     * @see org.apache.maven.api.services.ArtifactDeployer#deploy(Session, 
RemoteRepository, Collection)
+     * @param repository the repository where to deploy artifacts
+     * @param artifacts the artifacts to deploy
      * @throws org.apache.maven.api.services.ArtifactDeployerException if the 
artifacts deployment failed
+     *
+     * @see org.apache.maven.api.services.ArtifactDeployer#deploy(Session, 
RemoteRepository, Collection)
      */
-    void deployArtifact(RemoteRepository repository, Artifact... artifacts);
+    void deployArtifact(@Nonnull RemoteRepository repository, @Nonnull 
Artifact... artifacts);
 
     /**
      * Shortcut for {@code getService(ArtifactManager.class).setPath(...)}.
      *
+     * @param artifact the artifact for which to associate a path
+     * @param path path to associate to the given artifact
+     *
      * @see org.apache.maven.api.services.ArtifactManager#setPath(Artifact, 
Path)
      */
     void setArtifactPath(@Nonnull Artifact artifact, @Nonnull Path path);
@@ -354,6 +439,9 @@ public interface Session {
     /**
      * Shortcut for {@code getService(ArtifactManager.class).getPath(...)}.
      *
+     * @param artifact the artifact for which to get a path
+     * @return path associated to the given artifact
+     *
      * @see org.apache.maven.api.services.ArtifactManager#getPath(Artifact)
      */
     @Nonnull
@@ -365,6 +453,9 @@ public interface Session {
      * <p>
      * Shortcut for {@code 
getService(LocalArtifactManager.class).getPathForLocalArtitact(...)}.
      *
+     * @param artifact the artifact for which to get a local path
+     * @return local path associated to the given artifact, or {@code null} if 
none
+     *
      * @see 
org.apache.maven.api.services.LocalRepositoryManager#getPathForLocalArtifact(Session,
 LocalRepository, Artifact)
      */
     Path getPathForLocalArtifact(@Nonnull Artifact artifact);
@@ -376,6 +467,10 @@ public interface Session {
      * <p>
      * Shortcut for {@code 
getService(LocalArtifactManager.class).getPathForRemoteArtifact(...)}.
      *
+     * @param remote the repository from where artifacts are downloaded
+     * @param artifact the artifact for which to get a path
+     * @return path associated to the given artifact
+     *
      * @see 
org.apache.maven.api.services.LocalRepositoryManager#getPathForRemoteArtifact(Session,
 LocalRepository, RemoteRepository, Artifact)
      */
     @Nonnull
@@ -389,6 +484,9 @@ public interface Session {
      * In case there is {@link Artifact} in scope, the recommended way to 
perform this check is
      * use of {@link Artifact#isSnapshot()} instead.
      *
+     * @param version artifact version
+     * @return whether the given version is a snapshot
+     *
      * @see org.apache.maven.api.services.VersionParser#isSnapshot(String)
      */
     boolean isVersionSnapshot(@Nonnull String version);
@@ -396,6 +494,9 @@ public interface Session {
     /**
      * Shortcut for {@code getService(DependencyCollector.class).collect(...)}
      *
+     * @param artifact artifact for which to get the dependencies, including 
transitive ones
+     * @return root node of the dependency graph for the given artifact
+     *
      * @see org.apache.maven.api.services.DependencyCollector#collect(Session, 
Artifact)
      * @throws org.apache.maven.api.services.DependencyCollectorException if 
the dependency collection failed
      */
@@ -405,6 +506,9 @@ public interface Session {
     /**
      * Shortcut for {@code getService(DependencyCollector.class).collect(...)}
      *
+     * @param project project for which to get the dependencies, including 
transitive ones
+     * @return root node of the dependency graph for the given project
+     *
      * @see org.apache.maven.api.services.DependencyCollector#collect(Session, 
Project)
      * @throws org.apache.maven.api.services.DependencyCollectorException if 
the dependency collection failed
      */
@@ -418,6 +522,9 @@ public interface Session {
      * <p>
      * Shortcut for {@code getService(DependencyCollector.class).resolve(...)}
      *
+     * @param dependency dependency for which to get transitive dependencies
+     * @return root node of the dependency graph for the given artifact
+     *
      * @see org.apache.maven.api.services.DependencyCollector#collect(Session, 
DependencyCoordinate)
      * @throws org.apache.maven.api.services.DependencyCollectorException if 
the dependency collection failed
      */
@@ -427,29 +534,91 @@ public interface Session {
     /**
      * Shortcut for {@code getService(DependencyResolver.class).flatten(...)}.
      *
-     * @see org.apache.maven.api.services.DependencyResolver#flatten(Session, 
Node, PathScope)
+     * @param node node for which to get a flattened list
+     * @param scope build path scope (main compile, test compile, etc.) of 
desired nodes
+     * @return flattened list of node with the given build path scope
      * @throws org.apache.maven.api.services.DependencyResolverException if 
the dependency flattening failed
+     *
+     * @see org.apache.maven.api.services.DependencyResolver#flatten(Session, 
Node, PathScope)
      */
     @Nonnull
     List<Node> flattenDependencies(@Nonnull Node node, @Nonnull PathScope 
scope);
 
+    /**
+     * Shortcut for {@code 
getService(DependencyResolver.class).resolve(...).getPaths()}.
+     *
+     * @param dependencyCoordinate coordinate of the dependency for which to 
get the paths
+     * @return paths to the transitive dependencies of the given dependency
+     *
+     * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, 
DependencyCoordinate)
+     */
     @Nonnull
     List<Path> resolveDependencies(@Nonnull DependencyCoordinate 
dependencyCoordinate);
 
+    /**
+     * Shortcut for {@code 
getService(DependencyResolver.class).resolve(...).getPaths()}.
+     *
+     * @param dependencyCoordinates coordinates of all dependency for which to 
get the paths
+     * @return paths to the transitive dependencies of the given dependencies
+     *
+     * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, 
List)
+     */
     @Nonnull
     List<Path> resolveDependencies(@Nonnull List<DependencyCoordinate> 
dependencyCoordinates);
 
+    /**
+     * Shortcut for {@code 
getService(DependencyResolver.class).resolve(...).getPaths()}.
+     *
+     * @param project the project for which to get dependencies
+     * @param scope build path scope (main compile, test compile, etc.) of 
desired paths
+     * @return paths to the transitive dependencies of the given project
+     *
+     * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, 
Project, PathScope)
+     */
     @Nonnull
     List<Path> resolveDependencies(@Nonnull Project project, @Nonnull 
PathScope scope);
 
+    /**
+     * Shortcut for {@code 
getService(DependencyResolver.class).resolve(...).getDispatchedPaths()}.
+     *
+     * @param dependencyCoordinate coordinate of the dependency for which to 
get the paths
+     * @param scope build path scope (main compile, test compile, etc.) of 
desired paths
+     * @param desiredTypes the type of paths to include in the result
+     * @return paths to the transitive dependencies of the given project
+     *
+     * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, 
Project, PathScope)
+     */
+    @Nonnull
+    Map<PathType, List<Path>> resolveDependencies(
+            @Nonnull DependencyCoordinate dependencyCoordinate,
+            @Nonnull PathScope scope,
+            @Nonnull Collection<PathType> desiredTypes);
+
+    /**
+     * Shortcut for {@code 
getService(DependencyResolver.class).resolve(...).getDispatchedPaths()}.
+     *
+     * @param project the project for which to get dependencies
+     * @param scope build path scope (main compile, test compile, etc.) of 
desired paths
+     * @param desiredTypes the type of paths to include in the result
+     * @return paths to the transitive dependencies of the given project
+     *
+     * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, 
Project, PathScope)
+     */
+    @Nonnull
+    Map<PathType, List<Path>> resolveDependencies(
+            @Nonnull Project project, @Nonnull PathScope scope, @Nonnull 
Collection<PathType> desiredTypes);
+
     /**
      * Resolves an artifact's meta version (if any) to a concrete version. For 
example, resolves "1.0-SNAPSHOT"
      * to "1.0-20090208.132618-23" or "RELEASE"/"LATEST" to "2.0".
      * <p>
      * Shortcut for {@code getService(VersionResolver.class).resolve(...)}
      *
-     * @see org.apache.maven.api.services.VersionResolver#resolve(Session, 
ArtifactCoordinate) (String)
+     * @param artifact the artifact for which to resolve the version
+     * @return resolved version of the given artifact
      * @throws org.apache.maven.api.services.VersionResolverException if the 
resolution failed
+     *
+     * @see org.apache.maven.api.services.VersionResolver#resolve(Session, 
ArtifactCoordinate) (String)
      */
     @Nonnull
     Version resolveVersion(@Nonnull ArtifactCoordinate artifact);
@@ -462,9 +631,10 @@ public interface Session {
      * In this case though, the result contains simply the (parsed) input 
version, regardless of the
      * repositories and their contents.
      *
+     * @param artifact the artifact for which to resolve the versions
      * @return a list of resolved {@code Version}s.
-     * @see 
org.apache.maven.api.services.VersionRangeResolver#resolve(Session, 
ArtifactCoordinate) (String)
      * @throws org.apache.maven.api.services.VersionRangeResolverException if 
the resolution failed
+     * @see 
org.apache.maven.api.services.VersionRangeResolver#resolve(Session, 
ArtifactCoordinate) (String)
      */
     @Nonnull
     List<Version> resolveVersionRange(@Nonnull ArtifactCoordinate artifact);
@@ -474,8 +644,10 @@ public interface Session {
      * <p>
      * Shortcut for {@code getService(VersionParser.class).parseVersion(...)}.
      *
-     * @see org.apache.maven.api.services.VersionParser#parseVersion(String)
+     * @param version the version string to parse
+     * @return the version parsed from the given string
      * @throws org.apache.maven.api.services.VersionParserException if the 
parsing failed
+     * @see org.apache.maven.api.services.VersionParser#parseVersion(String)
      */
     @Nonnull
     Version parseVersion(@Nonnull String version);
@@ -485,8 +657,10 @@ public interface Session {
      * <p>
      * Shortcut for {@code 
getService(VersionParser.class).parseVersionRange(...)}.
      *
-     * @see 
org.apache.maven.api.services.VersionParser#parseVersionRange(String)
+     * @param versionRange the version string to parse
+     * @return the version range parsed from the given string
      * @throws org.apache.maven.api.services.VersionParserException if the 
parsing failed
+     * @see 
org.apache.maven.api.services.VersionParser#parseVersionRange(String)
      */
     @Nonnull
     VersionRange parseVersionRange(@Nonnull String versionRange);
@@ -496,8 +670,10 @@ public interface Session {
      * <p>
      * Shortcut for {@code 
getService(VersionParser.class).parseVersionConstraint(...)}.
      *
-     * @see 
org.apache.maven.api.services.VersionParser#parseVersionConstraint(String)
+     * @param versionConstraint the version string to parse
+     * @return the version constraint parsed from the given string
      * @throws org.apache.maven.api.services.VersionParserException if the 
parsing failed
+     * @see 
org.apache.maven.api.services.VersionParser#parseVersionConstraint(String)
      */
     @Nonnull
     VersionConstraint parseVersionConstraint(@Nonnull String 
versionConstraint);
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java
index 45a46fd1a1..5f702b5ed7 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java
@@ -18,6 +18,8 @@
  */
 package org.apache.maven.api;
 
+import java.util.Set;
+
 import org.apache.maven.api.annotations.Experimental;
 import org.apache.maven.api.annotations.Immutable;
 import org.apache.maven.api.annotations.Nonnull;
@@ -30,7 +32,7 @@ import org.apache.maven.api.model.Dependency;
  * <p>
  * It provides information about the file type (or extension) of the 
associated artifact,
  * its default classifier, and how the artifact will be used in the build when 
creating
- * various build paths.
+ * class-paths or module-paths.
  * <p>
  * For example, the type {@code java-source} has a {@code jar} extension and a
  * {@code sources} classifier. The artifact and its dependencies should be 
added
@@ -41,6 +43,60 @@ import org.apache.maven.api.model.Dependency;
 @Experimental
 @Immutable
 public interface Type extends ExtensibleEnum {
+    /**
+     * Artifact type name for a POM file.
+     */
+    String POM = "pom";
+
+    /**
+     * Artifact type name for a BOM file.
+     */
+    String BOM = "bom";
+
+    /**
+     * Artifact type name for a JAR file that can be placed either on the 
class-path or on the module-path.
+     * The path (classes or modules) is chosen by the plugin, possibly using 
heuristic rules.
+     * This is the behavior of Maven 3.
+     */
+    String JAR = "jar";
+
+    /**
+     * Artifact type name for a JAR file to unconditionally place on the 
class-path.
+     * If the JAR is modular, its module information are ignored.
+     * This type is new in Maven 4.
+     */
+    String CLASSPATH_JAR = "classpath-jar";
+
+    /**
+     * Artifact type name for a JAR file to unconditionally place on the 
module-path.
+     * If the JAR is not modular, then it is loaded by Java as an unnamed 
module.
+     * This type is new in Maven 4.
+     */
+    String MODULAR_JAR = "modular-jar";
+
+    /**
+     * Artifact type name for source code packaged in a JAR file.
+     */
+    String JAVA_SOURCE = "java-source";
+
+    /**
+     * Artifact type name for javadoc packaged in a JAR file.
+     */
+    String JAVADOC = "javadoc";
+
+    /**
+     * Artifact type name for a Maven plugin.
+     */
+    String MAVEN_PLUGIN = "maven-plugin";
+
+    /**
+     * Artifact type name for a JAR file containing test classes. If the main 
artifact is placed on the class-path
+     * ({@value #JAR} or {@value #CLASSPATH_JAR} types), then the test 
artifact will also be placed on the class-path.
+     * Otherwise, if the main artifact is placed on the module-path ({@value 
#JAR} or {@value #MODULAR_JAR} types),
+     * then the test artifact will be added using {@code --patch-module} 
option.
+     */
+    String TEST_JAR = "test-jar";
+
     /**
      * Returns the dependency type id.
      * The id uniquely identifies this <i>dependency type</i>.
@@ -76,13 +132,6 @@ public interface Type extends ExtensibleEnum {
     @Nullable
     String getClassifier();
 
-    /**
-     * Specifies if the artifact should be added to the build path.
-     *
-     * @return if the artifact should be added to the build path
-     */
-    boolean isBuildPathConstituent();
-
     /**
      * Specifies if the artifact already embeds its own dependencies.
      * This is the case for JEE packages or similar artifacts such as
@@ -91,4 +140,19 @@ public interface Type extends ExtensibleEnum {
      * @return if the artifact's dependencies are included in the artifact
      */
     boolean isIncludesDependencies();
+
+    /**
+     * Types of path (class-path, module-path, …) where the dependency can be 
placed.
+     * For most deterministic builds, the array length should be 1. In such 
case,
+     * the dependency will be unconditionally placed on the specified type of 
path
+     * and no heuristic rule will be involved.
+     *
+     * <p>It is nevertheless common to specify two or more types of path. For 
example,
+     * a Java library may be compatible with either the class-path or the 
module-path,
+     * and the user may have provided no instruction about which type to use. 
In such
+     * case, the plugin may apply rules for choosing a path. See for example
+     * {@link JavaPathType#CLASSES} and {@link JavaPathType#MODULES}.</p>
+     */
+    @Nonnull
+    Set<PathType> getPathTypes();
 }
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
index 6d8f009353..2f410a8a30 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
@@ -20,10 +20,13 @@ package org.apache.maven.api.services;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Predicate;
 
 import org.apache.maven.api.Artifact;
 import org.apache.maven.api.DependencyCoordinate;
+import org.apache.maven.api.JavaPathType;
 import org.apache.maven.api.PathScope;
+import org.apache.maven.api.PathType;
 import org.apache.maven.api.Project;
 import org.apache.maven.api.Session;
 import org.apache.maven.api.annotations.Experimental;
@@ -37,6 +40,16 @@ public interface DependencyResolverRequest extends 
DependencyCollectorRequest {
     @Nonnull
     PathScope getPathScope();
 
+    /**
+     * Returns a filter for the types of path (class-path, module-path, …) 
accepted by the tool.
+     * For example, if a Java tools accepts only class-path elements, then the 
filter should return
+     * {@code true} for {@link JavaPathType#CLASSES} and {@code false} for 
{@link JavaPathType#MODULES}.
+     * If no filter is explicitly set, then the default is a filter accepting 
everything.
+     *
+     * @return a filter for the types of path (class-path, module-path, …) 
accepted by the tool
+     */
+    Predicate<PathType> getPathTypeFilter();
+
     @Nonnull
     static DependencyResolverRequestBuilder builder() {
         return new DependencyResolverRequestBuilder();
@@ -88,6 +101,8 @@ public interface DependencyResolverRequest extends 
DependencyCollectorRequest {
     class DependencyResolverRequestBuilder extends 
DependencyCollectorRequestBuilder {
         PathScope pathScope;
 
+        Predicate<PathType> pathTypeFilter;
+
         @Nonnull
         @Override
         public DependencyResolverRequestBuilder session(@Nonnull Session 
session) {
@@ -158,16 +173,53 @@ public interface DependencyResolverRequest extends 
DependencyCollectorRequest {
             return this;
         }
 
+        /**
+         * Filters the types of paths to include in the result.
+         * The result will contain only the paths of types for which the 
predicate returned {@code true}.
+         * It is recommended to apply a filter for retaining only the types of 
paths of interest,
+         * because it can resolve ambiguities when a path could be of many 
types.
+         *
+         * @param pathTypeFilter predicate evaluating whether a path type 
should be included in the result
+         * @return {@code this} for method call chaining
+         */
+        @Nonnull
+        public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull 
Predicate<PathType> pathTypeFilter) {
+            this.pathTypeFilter = pathTypeFilter;
+            return this;
+        }
+
+        /**
+         * Specifies the type of paths to include in the result. This is a 
convenience method for
+         * {@link #pathTypeFilter(Predicate)} using {@link 
Collection#contains(Object)} as the filter.
+         *
+         * @param desiredTypes the type of paths to include in the result
+         * @return {@code this} for method call chaining
+         */
+        @Nonnull
+        public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull 
Collection<PathType> desiredTypes) {
+            return pathTypeFilter(desiredTypes::contains);
+        }
+
         @Override
         public DependencyResolverRequest build() {
             return new DefaultDependencyResolverRequest(
-                    session, project, rootArtifact, root, dependencies, 
managedDependencies, verbose, pathScope);
+                    session,
+                    project,
+                    rootArtifact,
+                    root,
+                    dependencies,
+                    managedDependencies,
+                    verbose,
+                    pathScope,
+                    pathTypeFilter);
         }
 
         static class DefaultDependencyResolverRequest extends 
DefaultDependencyCollectorRequest
                 implements DependencyResolverRequest {
             private final PathScope pathScope;
 
+            private final Predicate<PathType> pathTypeFilter;
+
             DefaultDependencyResolverRequest(
                     Session session,
                     Project project,
@@ -176,9 +228,11 @@ public interface DependencyResolverRequest extends 
DependencyCollectorRequest {
                     Collection<DependencyCoordinate> dependencies,
                     Collection<DependencyCoordinate> managedDependencies,
                     boolean verbose,
-                    PathScope pathScope) {
+                    PathScope pathScope,
+                    Predicate<PathType> pathTypeFilter) {
                 super(session, project, rootArtifact, root, dependencies, 
managedDependencies, verbose);
                 this.pathScope = nonNull(pathScope, "pathScope cannot be 
null");
+                this.pathTypeFilter = (pathTypeFilter != null) ? 
pathTypeFilter : (t) -> true;
                 if (verbose) {
                     throw new IllegalArgumentException("verbose cannot be true 
for resolving dependencies");
                 }
@@ -189,6 +243,11 @@ public interface DependencyResolverRequest extends 
DependencyCollectorRequest {
             public PathScope getPathScope() {
                 return pathScope;
             }
+
+            @Override
+            public Predicate<PathType> getPathTypeFilter() {
+                return pathTypeFilter;
+            }
         }
     }
 }
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java
index 7932438500..cb1c32031d 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java
@@ -21,9 +21,11 @@ package org.apache.maven.api.services;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.apache.maven.api.Dependency;
 import org.apache.maven.api.Node;
+import org.apache.maven.api.PathType;
 import org.apache.maven.api.annotations.Experimental;
 import org.apache.maven.api.annotations.Nonnull;
 
@@ -32,13 +34,57 @@ public interface DependencyResolverResult extends 
DependencyCollectorResult {
 
     /**
      * The ordered list of the flattened dependency nodes.
+     *
+     * @return the ordered list of the flattened dependency nodes
      */
     @Nonnull
     List<Node> getNodes();
 
+    /**
+     * Returns the file paths of all dependencies, regardless on which tool 
option those paths should be placed.
+     * The returned list may contain a mix of Java class-path, Java 
module-path, and other types of path elements.
+     *
+     * @return the paths of all dependencies
+     */
     @Nonnull
     List<Path> getPaths();
 
+    /**
+     * Returns the file paths of all dependencies, dispatched according the 
tool options where to place them.
+     * The {@link PathType} keys identify, for example, {@code --class-path} 
or {@code --module-path} options.
+     * In the case of Java tools, the map may also contain {@code 
--patch-module} options, which are
+     * {@linkplain org.apache.maven.api.JavaPathType#patchModule(String) 
handled in a special way}.
+     *
+     * <p><b>Design note:</b>
+     * All types of path are determined together because they are sometime 
mutually exclusive.
+     * For example, an artifact of type {@value org.apache.maven.api.Type#JAR} 
can be placed
+     * either on the class-path or on the module-path. The project needs to 
make a choice
+     * (possibly using heuristic rules), then to add the dependency in only 
one of the options
+     * identified by {@link PathType}.</p>
+     *
+     * @return file paths to place on the different tool options
+     */
+    @Nonnull
+    Map<PathType, List<Path>> getDispatchedPaths();
+
     @Nonnull
     Map<Dependency, Path> getDependencies();
+
+    /**
+     * Formats the command-line option for the path of the specified type.
+     * The option are documented in {@link org.apache.maven.api.JavaPathType} 
enumeration values.
+     *
+     * @param type the desired type of path (class-path, module-path, …)
+     * @return the option to pass to Java tools
+     */
+    default Optional<String> formatOption(PathType type) {
+        List<Path> paths = getDispatchedPaths().get(type);
+        if (paths != null) {
+            String option = type.option(paths);
+            if (!option.isEmpty()) {
+                return Optional.of(option);
+            }
+        }
+        return Optional.empty();
+    }
 }
diff --git 
a/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java
 
b/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java
index 364e12425e..12f50ef31a 100644
--- 
a/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java
+++ 
b/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java
@@ -54,8 +54,15 @@ public interface ArtifactHandler {
     String getLanguage();
 
     /**
-     * IMPORTANT: this is WRONGLY NAMED method (and/or remnant for Maven2).
-     * Its meaning is "is added to build path", that is used to create 
classpath/modulepath/etc.
+     * Specifies if the artifact contains java classes and can be added to the 
classpath.
+     * Whether the artifact <em>should</em> be added to the classpath depends 
on other
+     * dependency properties.
+     *
+     * @return if the artifact <em>can</em> be added to the class path
+     *
+     * @deprecated A value of {@code true} does not mean that the dependency 
<em>should</em>
+     * be placed on the classpath. See {@code JavaPathType} instead for better 
analysis.
      */
+    @Deprecated
     boolean isAddedToClasspath();
 }
diff --git 
a/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java
 
b/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java
index 8f8dd820e0..f70db5bcbd 100644
--- 
a/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java
+++ 
b/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java
@@ -77,11 +77,13 @@ public class ArtifactHandlerMock implements ArtifactHandler 
{
         return language;
     }
 
+    @Deprecated
     public void setAddedToClasspath(boolean addedToClasspath) {
         this.addedToClasspath = addedToClasspath;
     }
 
     @Override
+    @Deprecated
     public boolean isAddedToClasspath() {
         return addedToClasspath;
     }
diff --git 
a/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java
 
b/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java
index 5e02488d07..93c90bd889 100644
--- 
a/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java
+++ 
b/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java
@@ -65,6 +65,7 @@ class TestArtifactHandler implements ArtifactHandler {
     }
 
     @Override
+    @Deprecated
     public boolean isAddedToClasspath() {
         return true;
     }
diff --git 
a/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java
 
b/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java
index 0a083732c0..198fe6fb6a 100644
--- 
a/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java
+++ 
b/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java
@@ -37,6 +37,7 @@ public class DefaultArtifactHandler implements 
ArtifactHandler {
 
     private String language;
 
+    @Deprecated
     private boolean addedToClasspath;
 
     /**
@@ -146,10 +147,12 @@ public class DefaultArtifactHandler implements 
ArtifactHandler {
     }
 
     @Override
+    @Deprecated
     public boolean isAddedToClasspath() {
         return addedToClasspath;
     }
 
+    @Deprecated
     public void setAddedToClasspath(final boolean addedToClasspath) {
         this.addedToClasspath = addedToClasspath;
     }
diff --git 
a/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java
 
b/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java
index 1084d8617f..2ad0541914 100644
--- 
a/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java
+++ 
b/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.maven.api.JavaPathType;
 import org.apache.maven.api.Type;
 import org.apache.maven.api.services.TypeRegistry;
 import org.apache.maven.artifact.handler.ArtifactHandler;
@@ -72,7 +73,8 @@ public class DefaultArtifactHandlerManager extends 
AbstractEventSpy implements A
                     null,
                     type.isIncludesDependencies(),
                     type.getLanguage().id(),
-                    type.isBuildPathConstituent());
+                    type.getPathTypes().contains(JavaPathType.CLASSES));
+            // TODO: watch out for module path
         });
 
         // Note: here, type decides is artifact added to "build path" (for 
example during resolution)
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
 
b/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
index 89f00b4138..2f10720eb9 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.maven.internal.aether;
 
+import org.apache.maven.api.PathType;
 import org.apache.maven.api.Type;
 import org.apache.maven.api.services.TypeRegistry;
 import org.apache.maven.repository.internal.type.DefaultType;
@@ -45,8 +46,8 @@ class TypeRegistryAdapter implements ArtifactTypeRegistry {
                     type.getLanguage(),
                     type.getExtension(),
                     type.getClassifier(),
-                    type.isBuildPathConstituent(),
-                    type.isIncludesDependencies());
+                    type.isIncludesDependencies(),
+                    type.getPathTypes().toArray(new PathType[0]));
         }
         return null;
     }
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java 
b/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
index 0558c6ac14..b85b5b816f 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
@@ -29,6 +29,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
 
 import org.apache.maven.api.*;
 import org.apache.maven.api.annotations.Nonnull;
@@ -353,7 +354,8 @@ public abstract class AbstractSession implements 
InternalSession {
     @Override
     public Map<Artifact, Path> resolveArtifacts(Artifact... artifacts) {
         ArtifactCoordinateFactory acf = 
getService(ArtifactCoordinateFactory.class);
-        List<ArtifactCoordinate> coords = map(Arrays.asList(artifacts), a -> 
acf.create(this, a));
+        List<ArtifactCoordinate> coords =
+                Arrays.stream(artifacts).map(a -> acf.create(this, 
a)).collect(Collectors.toList());
         return resolveArtifacts(coords);
     }
 
@@ -502,6 +504,34 @@ public abstract class AbstractSession implements 
InternalSession {
                 .getPaths();
     }
 
+    @Override
+    public Map<PathType, List<Path>> resolveDependencies(
+            @Nonnull DependencyCoordinate dependency,
+            @Nonnull PathScope scope,
+            @Nonnull Collection<PathType> desiredTypes) {
+        return getService(DependencyResolver.class)
+                .resolve(DependencyResolverRequest.builder()
+                        .session(this)
+                        .dependency(dependency)
+                        .pathScope(scope)
+                        .pathTypeFilter(desiredTypes)
+                        .build())
+                .getDispatchedPaths();
+    }
+
+    @Override
+    public Map<PathType, List<Path>> resolveDependencies(
+            @Nonnull Project project, @Nonnull PathScope scope, @Nonnull 
Collection<PathType> desiredTypes) {
+        return getService(DependencyResolver.class)
+                .resolve(DependencyResolverRequest.builder()
+                        .session(this)
+                        .project(project)
+                        .pathScope(scope)
+                        .pathTypeFilter(desiredTypes)
+                        .build())
+                .getDispatchedPaths();
+    }
+
     @Override
     public Path getPathForLocalArtifact(@Nonnull Artifact artifact) {
         return 
getService(LocalRepositoryManager.class).getPathForLocalArtifact(this, 
getLocalRepository(), artifact);
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
index 5a5b3f9b05..8137a2e2c9 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
@@ -34,6 +34,7 @@ import org.eclipse.aether.artifact.ArtifactProperties;
 import static org.apache.maven.internal.impl.Utils.nonNull;
 
 public class DefaultDependency implements Dependency {
+
     private final InternalSession session;
     private final org.eclipse.aether.graph.Dependency dependency;
     private final String key;
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java
index 391075dd6b..37eac68cee 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java
@@ -21,24 +21,23 @@ package org.apache.maven.internal.impl;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
+import java.io.IOException;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.apache.maven.api.*;
+import org.apache.maven.api.Artifact;
+import org.apache.maven.api.ArtifactCoordinate;
+import org.apache.maven.api.Dependency;
+import org.apache.maven.api.Node;
+import org.apache.maven.api.PathType;
+import org.apache.maven.api.Session;
 import org.apache.maven.api.services.*;
-import org.apache.maven.lifecycle.LifecycleExecutionException;
-import org.apache.maven.lifecycle.internal.LifecycleDependencyResolver;
-import org.apache.maven.project.DependencyResolutionResult;
-import org.apache.maven.project.MavenProject;
 import org.eclipse.aether.graph.DependencyFilter;
 import org.eclipse.aether.graph.DependencyNode;
 
@@ -69,106 +68,43 @@ public class DefaultDependencyResolver implements 
DependencyResolver {
         };
     }
 
+    /**
+     * Collects, flattens and resolves the dependencies.
+     *
+     * @param request the request to resolve
+     * @return the result of the resolution
+     */
     @Override
     public DependencyResolverResult resolve(DependencyResolverRequest request)
             throws DependencyCollectorException, DependencyResolverException, 
ArtifactResolverException {
-        nonNull(request, "request");
-        Session session = InternalSession.from(request.getSession());
-
+        InternalSession session =
+                InternalSession.from(nonNull(request, "request").getSession());
+        Predicate<PathType> filter = request.getPathTypeFilter();
+        PathModularizationCache cache = new PathModularizationCache(); // 
TODO: should be project-wide cache.
         DependencyCollectorResult collectorResult =
                 session.getService(DependencyCollector.class).collect(request);
         List<Node> nodes = flatten(session, collectorResult.getRoot(), 
request.getPathScope());
-        List<Dependency> deps =
-                
nodes.stream().map(Node::getDependency).filter(Objects::nonNull).collect(Collectors.toList());
-        List<ArtifactCoordinate> coordinates =
-                
deps.stream().map(Artifact::toCoordinate).collect(Collectors.toList());
+        List<ArtifactCoordinate> coordinates = nodes.stream()
+                .map(Node::getDependency)
+                .filter(Objects::nonNull)
+                .map(Artifact::toCoordinate)
+                .collect(Collectors.toList());
         Map<Artifact, Path> artifacts = session.resolveArtifacts(coordinates);
-        Map<Dependency, Path> dependencies = new LinkedHashMap<>();
-        List<Path> paths = new ArrayList<>();
-        for (Dependency d : deps) {
-            Path path = artifacts.get(d);
-            if (dependencies.put(d, path) != null) {
-                throw new IllegalStateException("Duplicate key");
+        DefaultDependencyResolverResult result = new 
DefaultDependencyResolverResult(
+                collectorResult.getExceptions(), collectorResult.getRoot(), 
nodes.size());
+        for (Node node : nodes) {
+            Dependency d = node.getDependency();
+            Path path = (d != null) ? artifacts.get(d) : null;
+            try {
+                result.addDependency(node, d, filter, path, cache);
+            } catch (IOException e) {
+                throw cannotReadModuleInfo(path, e);
             }
-            paths.add(path);
-        }
-
-        return new DefaultDependencyResolverResult(
-                collectorResult.getExceptions(), collectorResult.getRoot(), 
nodes, paths, dependencies);
-    }
-
-    private Stream<DependencyNode> stream(DependencyNode node) {
-        return Stream.concat(Stream.of(node), 
node.getChildren().stream().flatMap(this::stream));
-    }
-
-    private DependencyResolutionResult resolveDependencies(Session session, 
Project project, PathScope scope) {
-        Collection<String> toResolve = toScopes(scope);
-        try {
-            LifecycleDependencyResolver lifecycleDependencyResolver =
-                    
session.getService(Lookup.class).lookup(LifecycleDependencyResolver.class);
-            return 
lifecycleDependencyResolver.getProjectDependencyResolutionResult(
-                    getMavenProject(project),
-                    toResolve,
-                    toResolve,
-                    InternalSession.from(session).getMavenSession(),
-                    false,
-                    Collections.emptySet());
-        } catch (LifecycleExecutionException e) {
-            throw new DependencyResolverException("Unable to resolve project 
dependencies", e);
         }
+        return result;
     }
 
-    private MavenProject getMavenProject(Project project) {
-        return ((DefaultProject) project).getProject();
-    }
-
-    private Collection<String> toScopes(PathScope scope) {
-        return map(scope.dependencyScopes(), DependencyScope::id);
-    }
-
-    static class DefaultDependencyResolverResult implements 
DependencyResolverResult {
-        private final List<Exception> exceptions;
-        private final Node root;
-        private final List<Node> nodes;
-        private final List<Path> paths;
-        private final Map<Dependency, Path> dependencies;
-
-        DefaultDependencyResolverResult(
-                List<Exception> exceptions,
-                Node root,
-                List<Node> nodes,
-                List<Path> paths,
-                Map<Dependency, Path> dependencies) {
-            this.exceptions = exceptions;
-            this.root = root;
-            this.nodes = nodes;
-            this.paths = paths;
-            this.dependencies = dependencies;
-        }
-
-        @Override
-        public List<Exception> getExceptions() {
-            return exceptions;
-        }
-
-        @Override
-        public Node getRoot() {
-            return root;
-        }
-
-        @Override
-        public List<Node> getNodes() {
-            return nodes;
-        }
-
-        @Override
-        public List<Path> getPaths() {
-            return paths;
-        }
-
-        @Override
-        public Map<Dependency, Path> getDependencies() {
-            return dependencies;
-        }
+    private static DependencyResolverException cannotReadModuleInfo(final Path 
path, final IOException cause) {
+        return new DependencyResolverException("Cannot read module information 
of " + path, cause);
     }
 }
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java
new file mode 100644
index 0000000000..0214ae4c69
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.internal.impl;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.apache.maven.api.Dependency;
+import org.apache.maven.api.JavaPathType;
+import org.apache.maven.api.Node;
+import org.apache.maven.api.PathType;
+import org.apache.maven.api.services.DependencyResolverRequest;
+import org.apache.maven.api.services.DependencyResolverResult;
+
+/**
+ * The result of collecting dependencies with a dependency resolver.
+ * New instances are initially empty. Callers must populate with calls
+ * to the following methods, in that order:
+ *
+ * <ul>
+ *   <li>{@link #addOutputDirectory(Path, Path, PathModularizationCache)} 
(optional)</li>
+ *   <li>{@link #addDependency(Node, Dependency, Predicate, Path, 
PathModularizationCache)}</li>
+ * </ul>
+ *
+ * @see DefaultDependencyResolver#resolve(DependencyResolverRequest)
+ */
+class DefaultDependencyResolverResult implements DependencyResolverResult {
+    /**
+     * The exceptions that occurred while building the dependency graph.
+     */
+    private final List<Exception> exceptions;
+
+    /**
+     * The root node of the dependency graph.
+     */
+    private final Node root;
+
+    /**
+     * The ordered list of the flattened dependency nodes.
+     */
+    private final List<Node> nodes;
+
+    /**
+     * The file paths of all dependencies, regardless on which Java tool 
option those paths should be placed.
+     */
+    private final List<Path> paths;
+
+    /**
+     * The file paths of all dependencies, dispatched according the Java 
options where to place them.
+     */
+    private final Map<PathType, List<Path>> dispatchedPaths;
+
+    /**
+     * The dependencies together with the path to each dependency.
+     */
+    private final Map<Dependency, Path> dependencies;
+
+    /**
+     * Information about modules in the main output. This field is initially 
null and is set to a non-null
+     * value when the output directories have been set, or when it is too late 
for setting them.
+     */
+    private PathModularization outputModules;
+
+    /**
+     * Creates an initially empty result. Callers should add path elements by 
calls
+     * to {@link #addDependency(Node, Dependency, Predicate, Path, 
PathModularizationCache)}.
+     *
+     * @param exceptions the exceptions that occurred while building the 
dependency graph
+     * @param root the root node of the dependency graph
+     * @param count estimated number of dependencies
+     */
+    DefaultDependencyResolverResult(List<Exception> exceptions, Node root, int 
count) {
+        this.exceptions = exceptions;
+        this.root = root;
+        nodes = new ArrayList<>(count);
+        paths = new ArrayList<>(count);
+        dispatchedPaths = new LinkedHashMap<>();
+        dependencies = new LinkedHashMap<>(count + count / 3);
+    }
+
+    /**
+     * Adds the given path element to the specified type of path.
+     *
+     * @param type the type of path (class-path, module-path, …)
+     * @param path the path element to add
+     */
+    private void addPathElement(PathType type, Path path) {
+        dispatchedPaths.computeIfAbsent(type, (t) -> new 
ArrayList<>()).add(path);
+    }
+
+    /**
+     * Adds main and test output directories to the result. This method adds 
the main output directory
+     * to the module-path if it contains a {@code module-info.class}, or to 
the class-path otherwise.
+     * For the test output directory, the rules are more complex and are 
governed by the fact that
+     * Java does not accept the placement of two modules of the same name on 
the module-path.
+     * So the modular test output directory usually needs to be placed in a 
{@code --path-module} option.
+     *
+     * <ul>
+     *   <li>If the test output directory is modular, then:
+     *     <ul>
+     *       <li>If a test module name is identical to a main module name,
+     *           place the test directory in a {@code --patch-module} 
option.</li>
+     *       <li>Otherwise, place the test directory on the module-path. 
However, this case
+     *           (a module existing only in test output, not in main output) 
should be uncommon.</li>
+     *     </ul>
+     *   </li>
+     *   <li>Otherwise (test output contains no module information), then:
+     *     <ul>
+     *       <li>If the main output is on the module-path, place the test 
output
+     *           on a {@code --patch-module} option.</li>
+     *       <li>Otherwise (main output on the class-path), place the test 
output on the class-path too.</li>
+     *     </ul>
+     *   </li>
+     * </ul>
+     *
+     * This method must be invoked before {@link #addDependency(Node, 
Dependency, Predicate, Path, PathModularizationCache)}
+     * if output directories are desired on the class-path or module-path.
+     * This method can be invoked at most once.
+     *
+     * @param main the main output directory, or {@code null} if none
+     * @param test the test output directory, or {@code null} if none
+     * @param cache cache of module information about each dependency
+     * @throws IOException if an error occurred while reading module 
information
+     *
+     * TODO: this is currently not called
+     */
+    void addOutputDirectory(Path main, Path test, PathModularizationCache 
cache) throws IOException {
+        if (outputModules != null) {
+            throw new IllegalStateException("Output directories must be set 
first and only once.");
+        }
+        if (main != null) {
+            outputModules = cache.getModuleInfo(main);
+            addPathElement(outputModules.getPathType(), main);
+        } else {
+            outputModules = PathModularization.NONE;
+        }
+        if (test != null) {
+            boolean addToClasspath = true;
+            PathModularization testModules = cache.getModuleInfo(test);
+            boolean isModuleHierarchy = outputModules.isModuleHierarchy() || 
testModules.isModuleHierarchy();
+            for (String moduleName : outputModules.getModuleNames().values()) {
+                Path subdir = test;
+                if (isModuleHierarchy) {
+                    // If module hierarchy is used, the directory names shall 
be the module names.
+                    Path path = test.resolve(moduleName);
+                    if (!Files.isDirectory(path)) {
+                        // Main module without tests. It is okay.
+                        continue;
+                    }
+                    subdir = path;
+                }
+                // When the same module is found in main and test output, the 
latter is patching the former.
+                addPathElement(JavaPathType.patchModule(moduleName), subdir);
+                addToClasspath = false;
+            }
+            /*
+             * If the test output directory provides some modules of its own, 
add them.
+             * Except for this unusual case, tests should never be added to 
the module-path.
+             */
+            for (Map.Entry<Path, String> entry : 
testModules.getModuleNames().entrySet()) {
+                if (!outputModules.containsModule(entry.getValue())) {
+                    addPathElement(JavaPathType.MODULES, entry.getKey());
+                    addToClasspath = false;
+                }
+            }
+            if (addToClasspath) {
+                addPathElement(JavaPathType.CLASSES, test);
+            }
+        }
+    }
+
+    /**
+     * Adds a dependency to the result. This method populates the {@link 
#nodes}, {@link #paths},
+     * {@link #dispatchedPaths} and {@link #dependencies} collections with the 
given arguments.
+     *
+     * @param node the dependency node
+     * @param dep the dependency for the given node, or {@code null} if none
+     * @param filter filter the paths accepted by the tool which will consume 
the path.
+     * @param path the path to the dependency, or {@code null} if the 
dependency was null
+     * @param cache cache of module information about each dependency
+     * @throws IOException if an error occurred while reading module 
information
+     */
+    void addDependency(Node node, Dependency dep, Predicate<PathType> filter, 
Path path, PathModularizationCache cache)
+            throws IOException {
+        nodes.add(node);
+        if (dep == null) {
+            return;
+        }
+        if (dependencies.put(dep, path) != null) {
+            throw new IllegalStateException("Duplicated key: " + dep);
+        }
+        if (path == null) {
+            return;
+        }
+        paths.add(path);
+        /*
+         * Dispatch the dependency to class-path, module-path, patch-module 
path, etc.
+         * according the dependency properties. We need to process 
patch-module first,
+         * because this type depends on whether a module of the same name has 
already
+         * been added on the module-type.
+         */
+        // DependencyProperties properties = dep.getDependencyProperties();
+        Set<PathType> pathTypes = dep.getType().getPathTypes();
+        if (containsPatches(pathTypes)) {
+            if (outputModules == null) {
+                // For telling users that it is too late for setting the 
output directory.
+                outputModules = PathModularization.NONE;
+            }
+            PathType type = null;
+            for (Map.Entry<Path, String> info :
+                    cache.getModuleInfo(path).getModuleNames().entrySet()) {
+                String moduleName = info.getValue();
+                type = JavaPathType.patchModule(moduleName);
+                if (!containsModule(moduleName, cache)) {
+                    /*
+                     * Not patching an existing module. This case should be 
unusual. If it nevertheless
+                     * happens, add on class-path or module-path if allowed, 
or keep patching otherwise.
+                     * The latter case (keep patching) is okay if the main 
module will be defined later.
+                     */
+                    type = cache.selectPathType(pathTypes, filter, 
path).orElse(type);
+                }
+                addPathElement(type, info.getKey());
+                // There is usually no more than one element, but nevertheless 
allow multi-modules.
+            }
+            /*
+             * If the dependency has no module information, search for an 
artifact of the same groupId
+             * and artifactId. If one is found, we are patching that module. 
If none is found, add the
+             * dependency as a normal dependency.
+             */
+            if (type == null) {
+                Path main = findArtifactPath(dep.getGroupId(), 
dep.getArtifactId());
+                if (main != null) {
+                    for (Map.Entry<Path, String> info :
+                            
cache.getModuleInfo(main).getModuleNames().entrySet()) {
+                        type = JavaPathType.patchModule(info.getValue());
+                        addPathElement(type, info.getKey());
+                        // There is usually no more than one element, but 
nevertheless allow multi-modules.
+                    }
+                }
+            }
+            if (type != null) {
+                return; // Dependency added, we are done.
+            }
+        }
+        addPathElement(cache.selectPathType(pathTypes, filter, 
path).orElse(PathType.UNRESOLVED), path);
+    }
+
+    /**
+     * Returns whether the given set of path types contains at least one patch 
for a module.
+     */
+    private boolean containsPatches(Set<PathType> types) {
+        for (PathType type : types) {
+            if (type instanceof JavaPathType.Modular) {
+                type = ((JavaPathType.Modular) type).rawType();
+            }
+            if (JavaPathType.PATCH_MODULE.equals(type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether at least one previously added modular dependency 
contains a module of the given name.
+     *
+     * @param moduleName name of the module to search
+     * @param cache cache of module information about each dependency
+     */
+    private boolean containsModule(String moduleName, PathModularizationCache 
cache) throws IOException {
+        for (Path path : dispatchedPaths.getOrDefault(JavaPathType.MODULES, 
Collections.emptyList())) {
+            if (cache.getModuleInfo(path).containsModule(moduleName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Searches an artifact of the given group and artifact identifiers, and 
returns its path
+     *
+     * @param group the group identifier to search
+     * @param artifact the artifact identifier to search
+     * @return path to the desired artifact, or {@code null} if not found
+     */
+    private Path findArtifactPath(String group, String artifact) throws 
IOException {
+        for (Map.Entry<Dependency, Path> entry : dependencies.entrySet()) {
+            Dependency dep = entry.getKey();
+            if (group.equals(dep.getGroupId()) && 
artifact.equals(dep.getArtifactId())) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<Exception> getExceptions() {
+        return exceptions;
+    }
+
+    @Override
+    public Node getRoot() {
+        return root;
+    }
+
+    @Override
+    public List<Node> getNodes() {
+        return nodes;
+    }
+
+    @Override
+    public List<Path> getPaths() {
+        return paths;
+    }
+
+    @Override
+    public Map<PathType, List<Path>> getDispatchedPaths() {
+        return dispatchedPaths;
+    }
+
+    @Override
+    public Map<Dependency, Path> getDependencies() {
+        return dependencies;
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java
index 5142f9f9c3..edc8c30103 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java
@@ -28,6 +28,7 @@ import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import org.apache.maven.api.JavaPathType;
 import org.apache.maven.api.Type;
 import org.apache.maven.api.annotations.Nonnull;
 import org.apache.maven.api.services.LanguageRegistry;
@@ -93,8 +94,9 @@ public class DefaultTypeRegistry extends AbstractEventSpy 
implements TypeRegistr
                         languageRegistry.require(handler.getLanguage()),
                         handler.getExtension(),
                         handler.getClassifier(),
-                        handler.isAddedToClasspath(),
-                        handler.isIncludesDependencies());
+                        handler.isIncludesDependencies(),
+                        JavaPathType.CLASSES,
+                        JavaPathType.MODULES);
             }
             return type;
         });
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularization.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularization.java
new file mode 100644
index 0000000000..44d32a8762
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularization.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.internal.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.lang.module.ModuleDescriptor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+
+import org.apache.maven.api.JavaPathType;
+import org.apache.maven.api.annotations.Nonnull;
+
+/**
+ * Information about the modules contained in a path element.
+ * The path element may be a JAR file or a directory. Directories may use 
either package hierarchy
+ * or module hierarchy, but not module source hierarchy. The latter is 
excluded because this class
+ * is for path elements of compiled codes.
+ */
+class PathModularization {
+    /**
+     * A unique constant for all non-modular dependencies.
+     */
+    public static final PathModularization NONE = new PathModularization();
+
+    /**
+     * Name of the file to use as a sentinel value for deciding if a directory 
or a JAR is modular.
+     */
+    private static final String MODULE_INFO = "module-info.class";
+
+    /**
+     * The attribute for automatic module name in {@code META-INF/MANIFEST.MF} 
files.
+     */
+    private static final Attributes.Name AUTO_MODULE_NAME = new 
Attributes.Name("Automatic-Module-Name");
+
+    /**
+     * Module information for the path specified at construction time.
+     * This map is usually either empty if no module was found, or a singleton 
map.
+     * It may however contain more than one entry if module hierarchy was 
detected,
+     * in which case there is one key per sub-directory.
+     *
+     * <p>This map may contain null values if the constructor was invoked with 
{@code resolve}
+     * parameter set to false. This is more efficient when only the module 
existence needs to
+     * be tested, and module descriptors are not needed.</p>
+     *
+     * @see #getModuleNames()
+     */
+    private final Map<Path, String> descriptors;
+
+    /**
+     * Whether module hierarchy was detected. If false, then package hierarchy 
is assumed.
+     * In a package hierarchy, the {@linkplain #descriptors} map has either 
zero or one entry.
+     * In a module hierarchy, the descriptors map may have an arbitrary number 
of entries,
+     * including one (so the map size cannot be used as a criterion).
+     *
+     * @see #isModuleHierarchy()
+     */
+    private final boolean isModuleHierarchy;
+
+    /**
+     * Constructs an empty instance for non-modular dependencies.
+     *
+     * @see #NONE
+     */
+    private PathModularization() {
+        descriptors = Collections.emptyMap();
+        isModuleHierarchy = false;
+    }
+
+    /**
+     * Finds module information in the given JAR file, output directory, or 
test output directory.
+     * If no module is found, or if module information cannot be extracted, 
then this constructor
+     * builds an empty map.
+     *
+     * <p>If the {@code resolve} parameter value is {@code false}, then some 
or all map values may
+     * be null instead of the actual module name. This option can avoid the 
cost of reading module
+     * descriptors when only the modules existence needs to be verified.</p>
+     *
+     * <p><b>Algorithm:</b>
+     * If the given path is a directory, then there is a choice:
+     * </p>
+     * <ul>
+     *   <li><b>Package hierarchy:</b> if a {@code module-info.class} file is 
found at the root,
+     *       then builds a singleton map with the module name declared in that 
descriptor.</li>
+     *   <li><b>Module hierarchy:</b> if {@code module-info.class} files are 
found in sub-directories,
+     *       at a deep intentionally restricted to one level, then builds a 
map of module names found
+     *       in the descriptor of each sub-directory.</li>
+     * </ul>
+     *
+     * Otherwise if the given path is a JAR file, then there is a choice:
+     * <ul>
+     *   <li>If a {@code module-info.class} file is found in the root 
directory or in a
+     *       {@code "META-INF/versions/{n}/"} subdirectory, builds a singleton 
map with
+     *       the module name declared in that descriptor.</li>
+     *   <li>Otherwise if an {@code "Automatic-Module-Name"} attribute is 
declared in the
+     *       {@code META-INF/MANIFEST.MF} file, builds a singleton map with 
the value of that attribute.</li>
+     * </ul>
+     *
+     * Otherwise builds an empty map.
+     *
+     * @param path directory or JAR file to test
+     * @param resolve whether the module names are requested. If false, null 
values may be used instead
+     * @throws IOException if an error occurred while reading the JAR file or 
the module descriptor
+     */
+    PathModularization(Path path, boolean resolve) throws IOException {
+        if (Files.isDirectory(path)) {
+            /*
+             * Package hierarchy: only one module with descriptor at the root.
+             * This is the layout of output directories in projects using the
+             * classical (Java 8 and before) way to organize source files.
+             */
+            Path file = path.resolve(MODULE_INFO);
+            if (Files.isRegularFile(file)) {
+                String name = null;
+                if (resolve) {
+                    try (InputStream in = Files.newInputStream(file)) {
+                        name = getModuleName(in);
+                    }
+                }
+                descriptors = Collections.singletonMap(file, name);
+                isModuleHierarchy = false;
+                return;
+            }
+            /*
+             * Module hierarchy: many modules, one per directory, with 
descriptor at the root of the sub-directory.
+             * This is the layout of output directories in projects using the 
new (Java 9 and later) way to organize
+             * source files.
+             */
+            if (Files.isDirectory(file)) {
+                Map<Path, String> names = new HashMap<>();
+                try (Stream<Path> subdirs = Files.list(file)) {
+                    subdirs.filter(Files::isDirectory).forEach((subdir) -> {
+                        Path mf = subdir.resolve(MODULE_INFO);
+                        if (Files.isRegularFile(mf)) {
+                            String name = null;
+                            if (resolve) {
+                                try (InputStream in = 
Files.newInputStream(mf)) {
+                                    name = getModuleName(in);
+                                } catch (IOException e) {
+                                    throw new UncheckedIOException(e);
+                                }
+                            }
+                            names.put(mf, name);
+                        }
+                    });
+                } catch (UncheckedIOException e) {
+                    throw e.getCause();
+                }
+                if (!names.isEmpty()) {
+                    descriptors = Collections.unmodifiableMap(names);
+                    isModuleHierarchy = true;
+                    return;
+                }
+            }
+        } else if (Files.isRegularFile(path)) {
+            /*
+             * JAR file: can contain only one module, with descriptor at the 
root.
+             * If no descriptor, the "Automatic-Module-Name" manifest 
attribute is
+             * taken as a fallback.
+             */
+            try (JarFile jar = new JarFile(path.toFile())) {
+                ZipEntry entry = jar.getEntry(MODULE_INFO);
+                if (entry != null) {
+                    String name = null;
+                    if (resolve) {
+                        try (InputStream in = jar.getInputStream(entry)) {
+                            name = getModuleName(in);
+                        }
+                    }
+                    descriptors = Collections.singletonMap(path, name);
+                    isModuleHierarchy = false;
+                    return;
+                }
+                // No module descriptor, check manifest file.
+                Manifest mf = jar.getManifest();
+                if (mf != null) {
+                    Object name = mf.getMainAttributes().get(AUTO_MODULE_NAME);
+                    if (name instanceof String) {
+                        descriptors = Collections.singletonMap(path, (String) 
name);
+                        isModuleHierarchy = false;
+                        return;
+                    }
+                }
+            }
+        }
+        descriptors = Collections.emptyMap();
+        isModuleHierarchy = false;
+    }
+
+    /**
+     * Returns the module name declared in the given {@code module-info} 
descriptor.
+     * The input stream may be for a file or for an entry in a JAR file.
+     */
+    @Nonnull
+    private static String getModuleName(InputStream in) throws IOException {
+        return ModuleDescriptor.read(in).name();
+    }
+
+    /**
+     * Returns the type of path detected. The return value is {@link 
JavaPathType#MODULES}
+     * if the dependency is a modular JAR file or a directory containing 
module descriptor(s),
+     * or {@link JavaPathType#CLASSES} otherwise. A JAR file without module 
descriptor but with
+     * an "Automatic-Module-Name" manifest attribute is considered modular.
+     */
+    public JavaPathType getPathType() {
+        return descriptors.isEmpty() ? JavaPathType.CLASSES : 
JavaPathType.MODULES;
+    }
+
+    /**
+     * Returns whether module hierarchy was detected. If false, then package 
hierarchy is assumed.
+     * In a package hierarchy, the {@linkplain #getModuleNames()} map of 
modules has either zero or one entry.
+     * In a module hierarchy, the descriptors map may have an arbitrary number 
of entries,
+     * including one (so the map size cannot be used as a criterion).
+     */
+    public boolean isModuleHierarchy() {
+        return isModuleHierarchy;
+    }
+
+    /**
+     * Returns the module names for the path specified at construction time.
+     * This map is usually either empty if no module was found, or a singleton 
map.
+     * It may however contain more than one entry if module hierarchy was 
detected,
+     * in which case there is one key per sub-directory.
+     *
+     * <p>This map may contain null values if the constructor was invoked with 
{@code resolve}
+     * parameter set to false. This is more efficient when only the module 
existence needs to
+     * be tested, and module descriptors are not needed.</p>
+     */
+    @Nonnull
+    public Map<Path, String> getModuleNames() {
+        return descriptors;
+    }
+
+    /**
+     * Returns whether the dependency contains a module of the given name.
+     */
+    public boolean containsModule(String name) {
+        return descriptors.containsValue(name);
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java
new file mode 100644
index 0000000000..e542fed68e
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.internal.impl;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.apache.maven.api.JavaPathType;
+import org.apache.maven.api.PathType;
+
+/**
+ * Cache of {@link PathModularization} instances computed for given {@link 
Path} elements.
+ * The cache is used for avoiding the need to reopen the same files many times 
when the
+ * same dependency is used for different scope. For example a path used for 
compilation
+ * is typically also used for tests.
+ */
+class PathModularizationCache {
+    /**
+     * Module information for each JAR file or output directories.
+     * Cached when first requested to avoid decoding the module descriptors 
multiple times.
+     *
+     * @see #getModuleInfo(Path)
+     */
+    private final Map<Path, PathModularization> moduleInfo;
+
+    /**
+     * Whether JAR files are modular. This map is redundant with {@link 
#moduleInfo},
+     * but cheaper to compute when the module names are not needed.
+     *
+     * @see #getPathType(Path)
+     */
+    private final Map<Path, PathType> pathTypes;
+
+    /**
+     * Creates an initially empty cache.
+     */
+    PathModularizationCache() {
+        moduleInfo = new HashMap<>();
+        pathTypes = new HashMap<>();
+    }
+
+    /**
+     * Gets module information for the given JAR file or output directory.
+     * Module descriptors are read when first requested, then cached.
+     */
+    PathModularization getModuleInfo(Path path) throws IOException {
+        PathModularization info = moduleInfo.get(path);
+        if (info == null) {
+            info = new PathModularization(path, true);
+            moduleInfo.put(path, info);
+            pathTypes.put(path, info.getPathType());
+        }
+        return info;
+    }
+
+    /**
+     * Returns {@link JavaPathType#MODULES} if the given JAR file or output 
directory is modular.
+     * This is used in heuristic rules for deciding whether to place a 
dependency on the class-path
+     * or on the module-path when the {@code "jar"} artifact type is used.
+     */
+    private PathType getPathType(Path path) throws IOException {
+        PathType type = pathTypes.get(path);
+        if (type == null) {
+            type = new PathModularization(path, false).getPathType();
+            pathTypes.put(path, type);
+        }
+        return type;
+    }
+
+    /**
+     * Selects the type of path where to place the given dependency.
+     * This method returns one of the values specified in the given collection.
+     * This method does not handle the patch-module paths, because the patches
+     * depend on which modules have been previously added on the module-paths.
+     *
+     * <p>If the dependency can be a constituent of both the class-path and 
the module-path,
+     * then the path type is determined by checking if the dependency is 
modular.</p>
+     *
+     * @param types types of path where a dependency can be placed
+     * @param filter filter the paths accepted by the tool which will consume 
the path
+     * @param path path to the JAR file or output directory of the dependency
+     * @return where to place the dependency, or an empty value if the 
placement cannot be determined
+     * @throws IOException if an error occurred while reading module 
information
+     */
+    Optional<PathType> selectPathType(Set<PathType> types, Predicate<PathType> 
filter, Path path) throws IOException {
+        PathType selected = null;
+        boolean classes = false;
+        boolean modules = false;
+        boolean unknown = false;
+        for (PathType type : types) {
+            if (filter.test(type)) {
+                if (JavaPathType.CLASSES.equals(type)) {
+                    classes = true;
+                } else if (JavaPathType.MODULES.equals(type)) {
+                    modules = true;
+                } else {
+                    unknown = true;
+                }
+                if (selected == null) {
+                    selected = type;
+                } else if (unknown) {
+                    // More than one filtered value, and we don't know how to 
handle at least one of them.
+                    // TODO: add a plugin mechanism for allowing plugin to 
specify their selection algorithm.
+                    return Optional.empty();
+                }
+            }
+        }
+        if (classes & modules) {
+            selected = getPathType(path);
+        }
+        return Optional.ofNullable(selected);
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
index b2a1f9c274..64087c262d 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java
@@ -62,6 +62,7 @@ class TransformedArtifactHandler implements ArtifactHandler {
     }
 
     @Override
+    @Deprecated
     public boolean isAddedToClasspath() {
         return false;
     }
diff --git 
a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java 
b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
index 987130aad4..42b08f5288 100644
--- a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
+++ b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
@@ -32,12 +32,14 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
+import java.util.function.Predicate;
 
 import org.apache.maven.RepositoryUtils;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.ArtifactUtils;
 import org.apache.maven.artifact.DependencyResolutionRequiredException;
 import org.apache.maven.artifact.factory.ArtifactFactory;
+import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
 import org.apache.maven.lifecycle.internal.DefaultProjectArtifactFactory;
@@ -320,68 +322,97 @@ public class MavenProject implements Cloneable {
         return testCompileSourceRoots;
     }
 
-    public List<String> getCompileClasspathElements() throws 
DependencyResolutionRequiredException {
-        List<String> list = new ArrayList<>(getArtifacts().size() + 1);
-
-        String d = getBuild().getOutputDirectory();
-        if (d != null) {
-            list.add(d);
-        }
-
-        for (Artifact a : getArtifacts()) {
-            if (a.getArtifactHandler().isAddedToClasspath()) {
-                // TODO let the scope handler deal with this
-                if (Artifact.SCOPE_COMPILE.equals(a.getScope())
-                        || Artifact.SCOPE_PROVIDED.equals(a.getScope())
-                        || Artifact.SCOPE_SYSTEM.equals(a.getScope())) {
-                    addArtifactPath(a, list);
-                }
-            }
-        }
+    // TODO let the scope handler deal with this
+    private static boolean isCompilePathElement(final String scope) {
+        return Artifact.SCOPE_COMPILE.equals(scope)
+                || Artifact.SCOPE_PROVIDED.equals(scope)
+                || Artifact.SCOPE_SYSTEM.equals(scope);
+    }
 
-        return list;
+    // TODO let the scope handler deal with this
+    private static boolean isRuntimePathElement(final String scope) {
+        return Artifact.SCOPE_COMPILE.equals(scope) || 
Artifact.SCOPE_RUNTIME.equals(scope);
     }
 
-    // TODO this checking for file == null happens because the resolver has 
been confused about the root
-    // artifact or not. things like the stupid dummy artifact coming from 
surefire.
-    public List<String> getTestClasspathElements() throws 
DependencyResolutionRequiredException {
-        List<String> list = new ArrayList<>(getArtifacts().size() + 2);
+    // TODO let the scope handler deal with this
+    private static boolean isTestPathElement(final String scope) {
+        return true;
+    }
 
-        String d = getBuild().getTestOutputDirectory();
-        if (d != null) {
-            list.add(d);
+    /**
+     * Returns a filtered list of classpath elements. This method is invoked 
when the caller
+     * requested that all dependencies are placed on the classpath, with no 
module-path element.
+     *
+     * @param scopeFilter a filter returning {@code true} for the artifact 
scopes to accept.
+     * @param includeTestDir whether to include the test directory in the 
classpath elements.
+     * @return paths of all artifacts placed on the classpath.
+     * @throws DependencyResolutionRequiredException if an artifact file is 
used, but has not been resolved
+     */
+    private List<String> getClasspathElements(final Predicate<String> 
scopeFilter, final boolean includeTestDir)
+            throws DependencyResolutionRequiredException {
+        final List<String> list = new ArrayList<>(getArtifacts().size() + 2);
+        if (includeTestDir) {
+            String d = getBuild().getTestOutputDirectory();
+            if (d != null) {
+                list.add(d);
+            }
         }
-
-        d = getBuild().getOutputDirectory();
+        String d = getBuild().getOutputDirectory();
         if (d != null) {
             list.add(d);
         }
-
         for (Artifact a : getArtifacts()) {
-            if (a.getArtifactHandler().isAddedToClasspath()) {
-                addArtifactPath(a, list);
+            final File f = a.getFile();
+            if (f != null && scopeFilter.test(a.getScope())) {
+                final ArtifactHandler h = a.getArtifactHandler();
+                if (h.isAddedToClasspath()) {
+                    list.add(f.getPath());
+                }
             }
         }
-
         return list;
     }
 
-    public List<String> getRuntimeClasspathElements() throws 
DependencyResolutionRequiredException {
-        List<String> list = new ArrayList<>(getArtifacts().size() + 1);
+    /**
+     * Returns the elements placed on the classpath for compilation.
+     * This method can be invoked when the caller does not support module-path.
+     *
+     * @throws DependencyResolutionRequiredException if an artifact file is 
used, but has not been resolved
+     *
+     * @deprecated This method is unreliable because it does not consider 
other dependency properties.
+     * See {@link org.apache.maven.api.JavaPathType} instead for better 
analysis.
+     */
+    @Deprecated
+    public List<String> getCompileClasspathElements() throws 
DependencyResolutionRequiredException {
+        return getClasspathElements(MavenProject::isCompilePathElement, false);
+    }
 
-        String d = getBuild().getOutputDirectory();
-        if (d != null) {
-            list.add(d);
-        }
+    /**
+     * Returns the elements placed on the classpath for tests.
+     * This method can be invoked when the caller does not support module-path.
+     *
+     * @throws DependencyResolutionRequiredException if an artifact file is 
used, but has not been resolved
+     *
+     * @deprecated This method is unreliable because it does not consider 
other dependency properties.
+     * See {@link org.apache.maven.api.JavaPathType} instead for better 
analysis.
+     */
+    @Deprecated
+    public List<String> getTestClasspathElements() throws 
DependencyResolutionRequiredException {
+        return getClasspathElements(MavenProject::isTestPathElement, true);
+    }
 
-        for (Artifact a : getArtifacts()) {
-            if (a.getArtifactHandler().isAddedToClasspath()
-                    // TODO let the scope handler deal with this
-                    && (Artifact.SCOPE_COMPILE.equals(a.getScope()) || 
Artifact.SCOPE_RUNTIME.equals(a.getScope()))) {
-                addArtifactPath(a, list);
-            }
-        }
-        return list;
+    /**
+     * Returns the elements placed on the classpath for runtime.
+     * This method can be invoked when the caller does not support module-path.
+     *
+     * @throws DependencyResolutionRequiredException if an artifact file is 
used, but has not been resolved
+     *
+     * @deprecated This method is unreliable because it does not consider 
other dependency properties.
+     * See {@link org.apache.maven.api.JavaPathType} instead for better 
analysis.
+     */
+    @Deprecated
+    public List<String> getRuntimeClasspathElements() throws 
DependencyResolutionRequiredException {
+        return getClasspathElements(MavenProject::isRuntimePathElement, false);
     }
 
     // ----------------------------------------------------------------------
@@ -1111,13 +1142,6 @@ public class MavenProject implements Cloneable {
         lifecyclePhases.addAll(project.lifecyclePhases);
     }
 
-    private void addArtifactPath(Artifact artifact, List<String> classpath) {
-        File file = artifact.getFile();
-        if (file != null) {
-            classpath.add(file.getPath());
-        }
-    }
-
     private static String getProjectReferenceId(String groupId, String 
artifactId, String version) {
         StringBuilder buffer = new StringBuilder(128);
         
buffer.append(groupId).append(':').append(artifactId).append(':').append(version);
@@ -1347,9 +1371,7 @@ public class MavenProject implements Cloneable {
             // TODO classpath check doesn't belong here - that's the other 
method
             if (a.getArtifactHandler().isAddedToClasspath()) {
                 // TODO let the scope handler deal with this
-                if (Artifact.SCOPE_COMPILE.equals(a.getScope())
-                        || Artifact.SCOPE_PROVIDED.equals(a.getScope())
-                        || Artifact.SCOPE_SYSTEM.equals(a.getScope())) {
+                if (isCompilePathElement(a.getScope())) {
                     list.add(a);
                 }
             }
@@ -1369,9 +1391,7 @@ public class MavenProject implements Cloneable {
 
         for (Artifact a : getArtifacts()) {
             // TODO let the scope handler deal with this
-            if (Artifact.SCOPE_COMPILE.equals(a.getScope())
-                    || Artifact.SCOPE_PROVIDED.equals(a.getScope())
-                    || Artifact.SCOPE_SYSTEM.equals(a.getScope())) {
+            if (isCompilePathElement(a.getScope())) {
                 Dependency dependency = new Dependency();
 
                 dependency.setArtifactId(a.getArtifactId());
@@ -1437,7 +1457,7 @@ public class MavenProject implements Cloneable {
 
         for (Artifact a : getArtifacts()) {
             // TODO let the scope handler deal with this
-            if (Artifact.SCOPE_COMPILE.equals(a.getScope()) || 
Artifact.SCOPE_RUNTIME.equals(a.getScope())) {
+            if (isRuntimePathElement(a.getScope())) {
                 Dependency dependency = new Dependency();
 
                 dependency.setArtifactId(a.getArtifactId());
@@ -1459,9 +1479,7 @@ public class MavenProject implements Cloneable {
 
         for (Artifact a : getArtifacts()) {
             // TODO classpath check doesn't belong here - that's the other 
method
-            if (a.getArtifactHandler().isAddedToClasspath()
-                    // TODO let the scope handler deal with this
-                    && (Artifact.SCOPE_COMPILE.equals(a.getScope()) || 
Artifact.SCOPE_RUNTIME.equals(a.getScope()))) {
+            if (a.getArtifactHandler().isAddedToClasspath() && 
isRuntimePathElement(a.getScope())) {
                 list.add(a);
             }
         }
@@ -1481,7 +1499,10 @@ public class MavenProject implements Cloneable {
             if (a.getArtifactHandler().isAddedToClasspath()) {
                 // TODO let the scope handler deal with this
                 if (Artifact.SCOPE_SYSTEM.equals(a.getScope())) {
-                    addArtifactPath(a, list);
+                    File f = a.getFile();
+                    if (f != null) {
+                        list.add(f.getPath());
+                    }
                 }
             }
         }
diff --git 
a/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java
 
b/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java
index bf266e717f..18c3442d06 100644
--- 
a/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java
+++ 
b/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java
@@ -83,6 +83,7 @@ public class PluginArtifact extends DefaultArtifact 
implements ArtifactWithDepen
         }
 
         @Override
+        @Deprecated
         public boolean isAddedToClasspath() {
             return true;
         }
diff --git 
a/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java
 
b/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java
index 965b370970..c040fc9344 100644
--- 
a/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java
+++ 
b/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java
@@ -88,6 +88,7 @@ public class ProjectArtifact extends DefaultArtifact 
implements ArtifactWithDepe
         }
 
         @Override
+        @Deprecated
         public boolean isAddedToClasspath() {
             return false;
         }
diff --git 
a/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java 
b/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java
index 3ebe1deb98..e4b74c9c57 100644
--- a/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java
+++ b/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java
@@ -23,6 +23,7 @@ import javax.inject.Inject;
 import java.io.File;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -58,6 +59,7 @@ import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @PlexusTest
@@ -126,6 +128,17 @@ class TestApi {
         sessionScope.seed(InternalSession.class, 
InternalSession.from(this.session));
     }
 
+    private Project project(Artifact artifact) {
+        return session.getService(ProjectBuilder.class)
+                .build(ProjectBuilderRequest.builder()
+                        .session(session)
+                        .path(session.getPathForLocalArtifact(artifact))
+                        .processPlugins(false)
+                        .build())
+                .getProject()
+                .get();
+    }
+
     @Test
     void testCreateAndResolveArtifact() {
         ArtifactCoordinate coord =
@@ -144,14 +157,7 @@ class TestApi {
     void testBuildProject() {
         Artifact artifact = session.createArtifact("org.codehaus.plexus", 
"plexus-utils", "1.4.5", "pom");
 
-        Project project = session.getService(ProjectBuilder.class)
-                .build(ProjectBuilderRequest.builder()
-                        .session(session)
-                        .path(session.getPathForLocalArtifact(artifact))
-                        .processPlugins(false)
-                        .build())
-                .getProject()
-                .get();
+        Project project = project(artifact);
         assertNotNull(project);
     }
 
@@ -165,28 +171,49 @@ class TestApi {
 
     @Test
     void testResolveArtifactCoordinateDependencies() {
-        ArtifactCoordinate coord =
-                session.createArtifactCoordinate("org.apache.maven.core.test", 
"test-extension", "1", "jar");
+        DependencyCoordinate coord = session.createDependencyCoordinate(
+                session.createArtifactCoordinate("org.apache.maven.core.test", 
"test-extension", "1", "jar"));
 
-        List<Path> paths = 
session.resolveDependencies(session.createDependencyCoordinate(coord));
+        List<Path> paths = session.resolveDependencies(coord);
 
         assertNotNull(paths);
         assertEquals(10, paths.size());
-        
assertTrue(paths.get(0).getFileName().toString().equals("test-extension-1.jar"));
+        assertEquals("test-extension-1.jar", 
paths.get(0).getFileName().toString());
+
+        // JUnit has an "Automatic-Module-Name", so it appears on the module 
path.
+        Map<PathType, List<Path>> dispatched = session.resolveDependencies(
+                coord, PathScope.TEST_COMPILE, 
Arrays.asList(JavaPathType.CLASSES, JavaPathType.MODULES));
+        List<Path> classes = dispatched.get(JavaPathType.CLASSES);
+        List<Path> modules = dispatched.get(JavaPathType.MODULES);
+        List<Path> unresolved = dispatched.get(PathType.UNRESOLVED);
+        assertEquals(3, dispatched.size());
+        assertEquals(1, unresolved.size());
+        assertEquals(8, classes.size()); // "plexus.pom" and "junit.jar" are 
excluded.
+        assertEquals(1, modules.size());
+        assertEquals("plexus-1.0.11.pom", 
unresolved.get(0).getFileName().toString());
+        assertEquals("test-extension-1.jar", 
classes.get(0).getFileName().toString());
+        assertEquals("junit-4.13.1.jar", 
modules.get(0).getFileName().toString());
+        assertTrue(paths.containsAll(classes));
+        assertTrue(paths.containsAll(modules));
+
+        // If caller wants only a classpath, JUnit shall move there.
+        dispatched = session.resolveDependencies(coord, 
PathScope.TEST_COMPILE, Arrays.asList(JavaPathType.CLASSES));
+        classes = dispatched.get(JavaPathType.CLASSES);
+        modules = dispatched.get(JavaPathType.MODULES);
+        unresolved = dispatched.get(PathType.UNRESOLVED);
+        assertEquals(2, dispatched.size());
+        assertEquals(1, unresolved.size());
+        assertEquals(9, classes.size());
+        assertNull(modules);
+        assertTrue(paths.containsAll(classes));
+        assertEquals("plexus-1.0.11.pom", 
unresolved.get(0).getFileName().toString());
     }
 
     @Test
     void testProjectDependencies() {
         Artifact pom = session.createArtifact("org.codehaus.plexus", 
"plexus-container-default", "1.0-alpha-32", "pom");
 
-        Project project = session.getService(ProjectBuilder.class)
-                .build(ProjectBuilderRequest.builder()
-                        .session(session)
-                        .path(session.getPathForLocalArtifact(pom))
-                        .processPlugins(false)
-                        .build())
-                .getProject()
-                .get();
+        Project project = project(pom);
         assertNotNull(project);
 
         Artifact artifact = 
session.createArtifact("org.apache.maven.core.test", "test-extension", "1", 
"jar");
diff --git 
a/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java
 
b/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java
index 68281e9169..30ebee3972 100644
--- 
a/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java
+++ 
b/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java
@@ -65,6 +65,7 @@ class TestArtifactHandler implements ArtifactHandler {
     }
 
     @Override
+    @Deprecated
     public boolean isAddedToClasspath() {
         return true;
     }
diff --git 
a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
 
b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
index 99c05e8363..1bbe98e366 100644
--- 
a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
+++ 
b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
@@ -95,7 +95,7 @@ public class ArtifactDescriptorReaderDelegate {
         ArtifactType stereotype = stereotypes.get(dependency.getType());
         if (stereotype == null) {
             // TODO: this here is fishy
-            stereotype = new DefaultType(dependency.getType(), Language.NONE, 
"", null, false, false);
+            stereotype = new DefaultType(dependency.getType(), Language.NONE, 
"", null, false);
         }
 
         boolean system = dependency.getSystemPath() != null
diff --git 
a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
 
b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
index d87836bfac..6096267ea1 100644
--- 
a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
+++ 
b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
@@ -18,11 +18,12 @@
  */
 package org.apache.maven.repository.internal.type;
 
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.*;
 import java.util.Map;
 
+import org.apache.maven.api.JavaPathType;
 import org.apache.maven.api.Language;
+import org.apache.maven.api.PathType;
 import org.apache.maven.api.Type;
 import org.apache.maven.repository.internal.artifact.MavenArtifactProperties;
 import org.eclipse.aether.artifact.ArtifactProperties;
@@ -40,8 +41,8 @@ public class DefaultType implements Type, ArtifactType {
     private final Language language;
     private final String extension;
     private final String classifier;
-    private final boolean buildPathConstituent;
     private final boolean includesDependencies;
+    private final Set<PathType> pathTypes;
     private final Map<String, String> properties;
 
     public DefaultType(
@@ -49,20 +50,22 @@ public class DefaultType implements Type, ArtifactType {
             Language language,
             String extension,
             String classifier,
-            boolean buildPathConstituent,
-            boolean includesDependencies) {
+            boolean includesDependencies,
+            PathType... pathTypes) {
         this.id = requireNonNull(id, "id");
         this.language = requireNonNull(language, "language");
         this.extension = requireNonNull(extension, "extension");
         this.classifier = classifier;
-        this.buildPathConstituent = buildPathConstituent;
         this.includesDependencies = includesDependencies;
+        this.pathTypes = Collections.unmodifiableSet(new 
HashSet<>(Arrays.asList(pathTypes)));
 
         Map<String, String> properties = new HashMap<>();
         properties.put(ArtifactProperties.TYPE, id);
         properties.put(ArtifactProperties.LANGUAGE, language.id());
         properties.put(MavenArtifactProperties.INCLUDES_DEPENDENCIES, 
Boolean.toString(includesDependencies));
-        properties.put(MavenArtifactProperties.CONSTITUTES_BUILD_PATH, 
Boolean.toString(buildPathConstituent));
+        properties.put(
+                MavenArtifactProperties.CONSTITUTES_BUILD_PATH,
+                String.valueOf(this.pathTypes.contains(JavaPathType.CLASSES)));
         this.properties = Collections.unmodifiableMap(properties);
     }
 
@@ -91,16 +94,15 @@ public class DefaultType implements Type, ArtifactType {
         return classifier;
     }
 
-    @Override
-    public boolean isBuildPathConstituent() {
-        return this.buildPathConstituent;
-    }
-
     @Override
     public boolean isIncludesDependencies() {
         return this.includesDependencies;
     }
 
+    public Set<PathType> getPathTypes() {
+        return this.pathTypes;
+    }
+
     @Override
     public Map<String, String> getProperties() {
         return properties;
diff --git 
a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java
 
b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java
index 2172fce0bb..fb833e687d 100644
--- 
a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java
+++ 
b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java
@@ -23,6 +23,7 @@ import javax.inject.Named;
 import java.util.Arrays;
 import java.util.Collection;
 
+import org.apache.maven.api.JavaPathType;
 import org.apache.maven.api.Language;
 import org.apache.maven.api.Type;
 import org.apache.maven.api.spi.TypeProvider;
@@ -37,18 +38,31 @@ public class DefaultTypeProvider implements TypeProvider {
 
     public Collection<DefaultType> types() {
         return Arrays.asList(
-                new DefaultType("bom", Language.NONE, "pom", null, false, 
false),
-                new DefaultType("pom", Language.NONE, "pom", null, false, 
false),
-                new DefaultType("maven-plugin", Language.JAVA_FAMILY, "jar", 
null, true, false),
-                new DefaultType("jar", Language.JAVA_FAMILY, "jar", null, 
true, false),
-                new DefaultType("ejb", Language.JAVA_FAMILY, "jar", null, 
true, false),
-                new DefaultType("ejb-client", Language.JAVA_FAMILY, "jar", 
"client", true, false),
-                new DefaultType("test-jar", Language.JAVA_FAMILY, "jar", 
"tests", true, false),
-                new DefaultType("javadoc", Language.JAVA_FAMILY, "jar", 
"javadoc", true, false),
-                new DefaultType("java-source", Language.JAVA_FAMILY, "jar", 
"sources", false, false),
-                new DefaultType("war", Language.JAVA_FAMILY, "war", null, 
false, true),
-                new DefaultType("ear", Language.JAVA_FAMILY, "ear", null, 
false, true),
-                new DefaultType("rar", Language.JAVA_FAMILY, "rar", null, 
false, true),
-                new DefaultType("par", Language.JAVA_FAMILY, "par", null, 
false, true));
+                // Maven types
+                new DefaultType(Type.POM, Language.NONE, "pom", null, false),
+                new DefaultType(Type.BOM, Language.NONE, "pom", null, false),
+                new DefaultType(Type.MAVEN_PLUGIN, Language.JAVA_FAMILY, 
"jar", null, false, JavaPathType.CLASSES),
+                // Java types
+                new DefaultType(
+                        Type.JAR, Language.JAVA_FAMILY, "jar", null, false, 
JavaPathType.CLASSES, JavaPathType.MODULES),
+                new DefaultType(Type.JAVADOC, Language.JAVA_FAMILY, "jar", 
"javadoc", false, JavaPathType.CLASSES),
+                new DefaultType(Type.JAVA_SOURCE, Language.JAVA_FAMILY, "jar", 
"sources", false),
+                new DefaultType(
+                        Type.TEST_JAR,
+                        Language.JAVA_FAMILY,
+                        "jar",
+                        "tests",
+                        false,
+                        JavaPathType.CLASSES,
+                        JavaPathType.PATCH_MODULE),
+                new DefaultType(Type.MODULAR_JAR, Language.JAVA_FAMILY, "jar", 
null, false, JavaPathType.MODULES),
+                new DefaultType(Type.CLASSPATH_JAR, Language.JAVA_FAMILY, 
"jar", null, false, JavaPathType.CLASSES),
+                // j2ee types
+                new DefaultType("ejb", Language.JAVA_FAMILY, "jar", null, 
false, JavaPathType.CLASSES),
+                new DefaultType("ejb-client", Language.JAVA_FAMILY, "jar", 
"client", false, JavaPathType.CLASSES),
+                new DefaultType("war", Language.JAVA_FAMILY, "war", null, 
true),
+                new DefaultType("ear", Language.JAVA_FAMILY, "ear", null, 
true),
+                new DefaultType("rar", Language.JAVA_FAMILY, "rar", null, 
true),
+                new DefaultType("par", Language.JAVA_FAMILY, "par", null, 
true));
     }
 }

Reply via email to