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

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


The following commit(s) were added to refs/heads/master by this push:
     new a6faf6d8 [MRESOLVER-302] Introduce onSessionClose (#357)
a6faf6d8 is described below

commit a6faf6d86f1270b36be4429f97f59310abfff9a6
Author: Tamas Cservenak <[email protected]>
AuthorDate: Thu Nov 9 11:22:20 2023 +0100

    [MRESOLVER-302] Introduce onSessionClose (#357)
    
    And make it used in new HTTP/2 transports.
    
    Note about implementation:
    * code base integrating Resolver 2.x (like Maven) SHOULD switch to new 
session handling
    * code base using resolver w/o managing it (like Maven Mojos are) does NOT 
have to move to new session handling (basically resolver for them work 
unchanged).
    
    In short, the Resolver 1.x session handling is still present, but is 
deprecated (DefaultRepositorySystemSession default ctor) and will emit warnings 
if application integrating Resolver 2.x still uses Resolver 1.x session 
handling and is about to use new Resolver 2.x features (like HTTP/2 transport) 
that require onSessionClose. Doing that will produce leaks, unless they are 
one-time CLI apps.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-302
---
 .../AbstractForwardingRepositorySystemSession.java |   5 +
 .../aether/DefaultRepositorySystemSession.java     |  24 +-
 .../java/org/eclipse/aether/RepositorySystem.java  |  24 +-
 .../eclipse/aether/RepositorySystemSession.java    | 372 +++++++++++++
 .../aether/impl/RepositorySystemLifecycle.java     |  31 ++
 .../internal/impl/DefaultRepositorySystem.java     |  17 +
 .../impl/DefaultRepositorySystemLifecycle.java     |  74 ++-
 .../DefaultCloseableRepositorySystemSession.java   | 343 ++++++++++++
 .../impl/session/DefaultSessionBuilder.java        | 580 +++++++++++++++++++++
 .../maven-resolver-transport-jdk-11/pom.xml        |   4 +
 .../maven-resolver-transport-jdk-19/pom.xml        |   4 +
 .../maven-resolver-transport-jdk-21/pom.xml        |   4 +
 .../jdk/JdkHttpTransporterCustomizer.java          |   8 +-
 .../maven-resolver-transport-jdk-8/pom.xml         |   4 +
 .../transport/jdk/JdkTransporterFactory.java       |   5 +
 .../maven-resolver-transport-jdk/pom.xml           |   4 +
 maven-resolver-transport-jetty/pom.xml             |   4 +
 .../aether/transport/jetty/JettyTransporter.java   |  14 +
 src/site/markdown/upgrading-resolver.md            |  24 +
 19 files changed, 1539 insertions(+), 6 deletions(-)

diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java
index 5e3a60d4..9b7652f6 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java
@@ -190,4 +190,9 @@ public abstract class 
AbstractForwardingRepositorySystemSession implements Repos
     public RepositoryCache getCache() {
         return getSession().getCache();
     }
+
+    @Override
+    public boolean addOnSessionEndedHandler(Runnable handler) {
+        return getSession().addOnSessionEndedHandler(handler);
+    }
 }
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java
index 3f46af54..5ad90103 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java
@@ -21,6 +21,7 @@ package org.eclipse.aether;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Function;
 
 import org.eclipse.aether.artifact.ArtifactType;
 import org.eclipse.aether.artifact.ArtifactTypeRegistry;
@@ -51,10 +52,9 @@ import static java.util.Objects.requireNonNull;
  * <strong>Note:</strong> This class is not thread-safe. It is assumed that 
the mutators get only called during an
  * initialization phase and that the session itself is not changed once 
initialized and being used by the repository
  * system. It is recommended to call {@link #setReadOnly()} once the session 
has been fully initialized to prevent
- * accidental manipulation of it afterwards.
+ * accidental manipulation of it afterward.
  */
 public final class DefaultRepositorySystemSession implements 
RepositorySystemSession {
-
     private boolean readOnly;
 
     private boolean offline;
@@ -113,11 +113,18 @@ public final class DefaultRepositorySystemSession 
implements RepositorySystemSes
 
     private RepositoryCache cache;
 
+    private final Function<Runnable, Boolean> onSessionEndedRegistrar;
+
     /**
      * Creates an uninitialized session. <em>Note:</em> The new session is not 
ready to use, as a bare minimum,
      * {@link #setLocalRepositoryManager(LocalRepositoryManager)} needs to be 
called but usually other settings also
      * need to be customized to achieve meaningful behavior.
+     *
+     * @deprecated This way of creating session should be avoided, is in place 
just to offer backward binary
+     * compatibility with Resolver 1.x using code, but offers reduced 
functionality.
+     * Use {@link RepositorySystem#createSessionBuilder()} instead.
      */
+    @Deprecated
     public DefaultRepositorySystemSession() {
         systemProperties = new HashMap<>();
         systemPropertiesView = Collections.unmodifiableMap(systemProperties);
@@ -130,6 +137,7 @@ public final class DefaultRepositorySystemSession 
implements RepositorySystemSes
         authenticationSelector = NullAuthenticationSelector.INSTANCE;
         artifactTypeRegistry = NullArtifactTypeRegistry.INSTANCE;
         data = new DefaultSessionData();
+        onSessionEndedRegistrar = h -> false;
     }
 
     /**
@@ -168,6 +176,7 @@ public final class DefaultRepositorySystemSession 
implements RepositorySystemSes
         setDependencyGraphTransformer(session.getDependencyGraphTransformer());
         setData(session.getData());
         setCache(session.getCache());
+        this.onSessionEndedRegistrar = session::addOnSessionEndedHandler;
     }
 
     @Override
@@ -766,6 +775,17 @@ public final class DefaultRepositorySystemSession 
implements RepositorySystemSes
         return this;
     }
 
+    /**
+     * Registers onSessionEnded handler, if able to.
+     *
+     * @param handler The handler to register
+     * @return Return {@code true} if registration was possible, otherwise 
{@code false}.
+     */
+    @Override
+    public boolean addOnSessionEndedHandler(Runnable handler) {
+        return onSessionEndedRegistrar.apply(handler);
+    }
+
     /**
      * Marks this session as read-only such that any future attempts to call 
its mutators will fail with an exception.
      * Marking an already read-only session as read-only has no effect. The 
session's data and cache remain writable
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java 
b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java
index 7253c209..0cc2f2cf 100644
--- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java
@@ -18,6 +18,7 @@
  */
 package org.eclipse.aether;
 
+import java.io.Closeable;
 import java.util.Collection;
 import java.util.List;
 
@@ -65,7 +66,7 @@ import org.eclipse.aether.resolution.VersionResult;
  * @noimplement This interface is not intended to be implemented by clients.
  * @noextend This interface is not intended to be extended by clients.
  */
-public interface RepositorySystem {
+public interface RepositorySystem extends Closeable {
 
     /**
      * Expands a version range to a list of matching versions, in ascending 
order. For example, resolves "[3.8,4.0)" to
@@ -277,7 +278,7 @@ public interface RepositorySystem {
      * {@link #deploy(RepositorySystemSession, DeployRequest) deploy()} is 
used as is and expected to already carry any
      * required authentication or proxy configuration. This method can be used 
to apply the authentication/proxy
      * configuration from a session to a bare repository definition to obtain 
the complete repository definition for use
-     * in the deploy request.
+     * in the deployment request.
      *
      * @param session    The repository system session from which to configure 
the repository, must not be {@code null}.
      * @param repository The repository prototype from which to derive the 
deployment repository, must not be
@@ -295,6 +296,15 @@ public interface RepositorySystem {
      */
     void addOnSystemEndedHandler(Runnable handler);
 
+    /**
+     * Creates a brand-new session builder instance that produces "top level" 
(root) session. Top level sessions are
+     * associated with its creator {@link RepositorySystem} instance, and may 
be used only with that given instance and
+     * only within the lifespan of it, and after use should be closed.
+     *
+     * @since TBD
+     */
+    RepositorySystemSession.SessionBuilder createSessionBuilder();
+
     /**
      * Signals to repository system to shut down. Shut down instance is not 
usable anymore.
      * <p>
@@ -308,4 +318,14 @@ public interface RepositorySystem {
      * @since 1.9.0
      */
     void shutdown();
+
+    /**
+     * Closes this instance, invokes {@link #shutdown()}.
+     *
+     * @since TBD
+     */
+    @Override
+    default void close() {
+        shutdown();
+    }
 }
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java
index c101621e..2f1ce20c 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java
@@ -18,6 +18,8 @@
  */
 package org.eclipse.aether;
 
+import java.io.Closeable;
+import java.io.File;
 import java.util.Map;
 
 import org.eclipse.aether.artifact.ArtifactTypeRegistry;
@@ -31,6 +33,7 @@ import org.eclipse.aether.repository.LocalRepository;
 import org.eclipse.aether.repository.LocalRepositoryManager;
 import org.eclipse.aether.repository.MirrorSelector;
 import org.eclipse.aether.repository.ProxySelector;
+import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.repository.RepositoryPolicy;
 import org.eclipse.aether.repository.WorkspaceReader;
 import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
@@ -48,6 +51,362 @@ import org.eclipse.aether.transfer.TransferListener;
  */
 public interface RepositorySystemSession {
 
+    /**
+     * Immutable session that is closeable, should be handled as a resource. 
These session instances can be
+     * created with {@link SessionBuilder}.
+     *
+     * @since TBD
+     */
+    interface CloseableRepositorySystemSession extends 
RepositorySystemSession, Closeable {
+        /**
+         * Returns the ID of this closeable session instance. Each closeable 
session has different ID, unique within
+         * repository system they were created with.
+         *
+         * @return The session ID that is never {@code null}.
+         */
+        String sessionId();
+
+        /**
+         * Copies this session into a pre-populated builder, effectively 
making a mutable copy of itself, builder builds
+         * <em>same session</em>. Important: this session <em>remains 
unchanged</em> upon return of this method but
+         * this session and returned builder created session will have 
<em>same identity</em>. It is up to client code,
+         * will it close only the original (this) session or new session, or 
both. Important is, that at least one of
+         * the sessions must be closed, and consequence is that once either 
one is closed, the other session is closed
+         * as well.
+         * <p>
+         * This pattern should be applied in "filter" like constructs, when 
code needs to alter the incoming session and
+         * subsequently pass it downstream.
+         */
+        SessionBuilder copy();
+
+        /**
+         * Closes the session. The session should be closed by its creator. A 
closed session should not be used anymore.
+         * This method may be invoked multiple times, but close will act only 
once (first time).
+         */
+        @Override
+        void close();
+    }
+
+    /**
+     * Builder for building {@link CloseableRepositorySystemSession} 
instances. Builder instances can be created with
+     * {@link RepositorySystem#createSessionBuilder()} method.
+     *
+     * @since TBD
+     */
+    interface SessionBuilder {
+        /**
+         * Controls whether the repository system operates in offline mode and 
avoids/refuses any access to remote
+         * repositories.
+         *
+         * @param offline {@code true} if the repository system is in offline 
mode, {@code false} otherwise.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setOffline(boolean offline);
+
+        /**
+         * Controls whether repositories declared in artifact descriptors 
should be ignored during transitive dependency
+         * collection. If enabled, only the repositories originally provided 
with the collect request will be considered.
+         *
+         * @param ignoreArtifactDescriptorRepositories {@code true} to ignore 
additional repositories from artifact
+         *                                             descriptors, {@code 
false} to merge those with the originally
+         *                                             specified repositories.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setIgnoreArtifactDescriptorRepositories(boolean 
ignoreArtifactDescriptorRepositories);
+
+        /**
+         * Sets the policy which controls whether resolutions errors from 
remote repositories should be cached.
+         *
+         * @param resolutionErrorPolicy The resolution error policy for this 
session, may be {@code null} if resolution
+         *                              errors should generally not be cached.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setResolutionErrorPolicy(ResolutionErrorPolicy 
resolutionErrorPolicy);
+
+        /**
+         * Sets the policy which controls how errors related to reading 
artifact descriptors should be handled.
+         *
+         * @param artifactDescriptorPolicy The descriptor error policy for 
this session, may be {@code null} if descriptor
+         *                                 errors should generally not be 
tolerated.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setArtifactDescriptorPolicy(ArtifactDescriptorPolicy 
artifactDescriptorPolicy);
+
+        /**
+         * Sets the global checksum policy. If set, the global checksum policy 
overrides the checksum policies of the remote
+         * repositories being used for resolution.
+         *
+         * @param checksumPolicy The global checksum policy, may be {@code 
null}/empty to apply the per-repository policies.
+         * @return This session for chaining, never {@code null}.
+         * @see RepositoryPolicy#CHECKSUM_POLICY_FAIL
+         * @see RepositoryPolicy#CHECKSUM_POLICY_IGNORE
+         * @see RepositoryPolicy#CHECKSUM_POLICY_WARN
+         */
+        SessionBuilder setChecksumPolicy(String checksumPolicy);
+
+        /**
+         * Sets the global update policy. If set, the global update policy 
overrides the update policies of the remote
+         * repositories being used for resolution.
+         * <p>
+         * This method is meant for code that does not want to distinguish 
between artifact and metadata policies.
+         * Note: applications should either use get/set updatePolicy (this 
method and
+         * {@link RepositorySystemSession#getUpdatePolicy()}) or also 
distinguish between artifact and
+         * metadata update policies (and use other methods), but <em>should 
not mix the two!</em>
+         *
+         * @param updatePolicy The global update policy, may be {@code 
null}/empty to apply the per-repository policies.
+         * @return This session for chaining, never {@code null}.
+         * @see RepositoryPolicy#UPDATE_POLICY_ALWAYS
+         * @see RepositoryPolicy#UPDATE_POLICY_DAILY
+         * @see RepositoryPolicy#UPDATE_POLICY_NEVER
+         * @see #setArtifactUpdatePolicy(String)
+         * @see #setMetadataUpdatePolicy(String)
+         */
+        SessionBuilder setUpdatePolicy(String updatePolicy);
+
+        /**
+         * Sets the global artifact update policy. If set, the global update 
policy overrides the artifact update policies
+         * of the remote repositories being used for resolution.
+         *
+         * @param artifactUpdatePolicy The global update policy, may be {@code 
null}/empty to apply the per-repository policies.
+         * @return This session for chaining, never {@code null}.
+         * @see RepositoryPolicy#UPDATE_POLICY_ALWAYS
+         * @see RepositoryPolicy#UPDATE_POLICY_DAILY
+         * @see RepositoryPolicy#UPDATE_POLICY_NEVER
+         * @since TBD
+         */
+        SessionBuilder setArtifactUpdatePolicy(String artifactUpdatePolicy);
+
+        /**
+         * Sets the global metadata update policy. If set, the global update 
policy overrides the metadata update policies
+         * of the remote repositories being used for resolution.
+         *
+         * @param metadataUpdatePolicy The global update policy, may be {@code 
null}/empty to apply the per-repository policies.
+         * @return This session for chaining, never {@code null}.
+         * @see RepositoryPolicy#UPDATE_POLICY_ALWAYS
+         * @see RepositoryPolicy#UPDATE_POLICY_DAILY
+         * @see RepositoryPolicy#UPDATE_POLICY_NEVER
+         * @since TBD
+         */
+        SessionBuilder setMetadataUpdatePolicy(String metadataUpdatePolicy);
+
+        /**
+         * Sets the local repository manager used during this session. 
<em>Note:</em> Eventually, a valid session must have
+         * a local repository manager set.
+         *
+         * @param localRepositoryManager The local repository manager used 
during this session, may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setLocalRepositoryManager(LocalRepositoryManager 
localRepositoryManager);
+
+        /**
+         * Sets the workspace reader used during this session. If set, the 
workspace reader will usually be consulted first
+         * to resolve artifacts.
+         *
+         * @param workspaceReader The workspace reader for this session, may 
be {@code null} if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setWorkspaceReader(WorkspaceReader workspaceReader);
+
+        /**
+         * Sets the listener being notified of actions in the repository 
system.
+         *
+         * @param repositoryListener The repository listener, may be {@code 
null} if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setRepositoryListener(RepositoryListener 
repositoryListener);
+
+        /**
+         * Sets the listener being notified of uploads/downloads by the 
repository system.
+         *
+         * @param transferListener The transfer listener, may be {@code null} 
if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setTransferListener(TransferListener transferListener);
+
+        /**
+         * Sets the system properties to use, e.g. for processing of artifact 
descriptors. System properties are usually
+         * collected from the runtime environment like {@link 
System#getProperties()} and environment variables.
+         * <p>
+         * <em>Note:</em> System properties are of type {@code Map<String, 
String>} and any key-value pair in the input map
+         * that doesn't match this type will be silently ignored.
+         *
+         * @param systemProperties The system properties, may be {@code null} 
or empty if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setSystemProperties(Map<?, ?> systemProperties);
+
+        /**
+         * Sets the specified system property.
+         *
+         * @param key   The property key, must not be {@code null}.
+         * @param value The property value, may be {@code null} to 
remove/unset the property.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setSystemProperty(String key, String value);
+
+        /**
+         * Sets the user properties to use, e.g. for processing of artifact 
descriptors. User properties are similar to
+         * system properties but are set on the discretion of the user and 
hence are considered of higher priority than
+         * system properties in case of conflicts.
+         * <p>
+         * <em>Note:</em> User properties are of type {@code Map<String, 
String>} and any key-value pair in the input map
+         * that doesn't match this type will be silently ignored.
+         *
+         * @param userProperties The user properties, may be {@code null} or 
empty if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setUserProperties(Map<?, ?> userProperties);
+
+        /**
+         * Sets the specified user property.
+         *
+         * @param key   The property key, must not be {@code null}.
+         * @param value The property value, may be {@code null} to 
remove/unset the property.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setUserProperty(String key, String value);
+
+        /**
+         * Sets the configuration properties used to tweak internal aspects of 
the repository system (e.g. thread pooling,
+         * connector-specific behavior, etc.).
+         * <p>
+         * <em>Note:</em> Configuration properties are of type {@code 
Map<String, Object>} and any key-value pair in the
+         * input map that doesn't match this type will be silently ignored.
+         *
+         * @param configProperties The configuration properties, may be {@code 
null} or empty if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setConfigProperties(Map<?, ?> configProperties);
+
+        /**
+         * Sets the specified configuration property.
+         *
+         * @param key   The property key, must not be {@code null}.
+         * @param value The property value, may be {@code null} to 
remove/unset the property.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setConfigProperty(String key, Object value);
+
+        /**
+         * Sets the mirror selector to use for repositories discovered in 
artifact descriptors. Note that this selector is
+         * not used for remote repositories which are passed as request 
parameters to the repository system, those
+         * repositories are supposed to denote the effective repositories.
+         *
+         * @param mirrorSelector The mirror selector to use, may be {@code 
null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setMirrorSelector(MirrorSelector mirrorSelector);
+
+        /**
+         * Sets the proxy selector to use for repositories discovered in 
artifact descriptors. Note that this selector is
+         * not used for remote repositories which are passed as request 
parameters to the repository system, those
+         * repositories are supposed to have their proxy (if any) already set.
+         *
+         * @param proxySelector The proxy selector to use, may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         * @see RemoteRepository#getProxy()
+         */
+        SessionBuilder setProxySelector(ProxySelector proxySelector);
+
+        /**
+         * Sets the authentication selector to use for repositories discovered 
in artifact descriptors. Note that this
+         * selector is not used for remote repositories which are passed as 
request parameters to the repository system,
+         * those repositories are supposed to have their authentication (if 
any) already set.
+         *
+         * @param authenticationSelector The authentication selector to use, 
may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         * @see RemoteRepository#getAuthentication()
+         */
+        SessionBuilder setAuthenticationSelector(AuthenticationSelector 
authenticationSelector);
+
+        /**
+         * Sets the registry of artifact types recognized by this session.
+         *
+         * @param artifactTypeRegistry The artifact type registry, may be 
{@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setArtifactTypeRegistry(ArtifactTypeRegistry 
artifactTypeRegistry);
+
+        /**
+         * Sets the dependency traverser to use for building dependency graphs.
+         *
+         * @param dependencyTraverser The dependency traverser to use for 
building dependency graphs, may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setDependencyTraverser(DependencyTraverser 
dependencyTraverser);
+
+        /**
+         * Sets the dependency manager to use for building dependency graphs.
+         *
+         * @param dependencyManager The dependency manager to use for building 
dependency graphs, may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setDependencyManager(DependencyManager 
dependencyManager);
+
+        /**
+         * Sets the dependency selector to use for building dependency graphs.
+         *
+         * @param dependencySelector The dependency selector to use for 
building dependency graphs, may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setDependencySelector(DependencySelector 
dependencySelector);
+
+        /**
+         * Sets the version filter to use for building dependency graphs.
+         *
+         * @param versionFilter The version filter to use for building 
dependency graphs, may be {@code null} to not filter
+         *                      versions.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setVersionFilter(VersionFilter versionFilter);
+
+        /**
+         * Sets the dependency graph transformer to use for building 
dependency graphs.
+         *
+         * @param dependencyGraphTransformer The dependency graph transformer 
to use for building dependency graphs, may be
+         *                                   {@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder 
setDependencyGraphTransformer(DependencyGraphTransformer 
dependencyGraphTransformer);
+
+        /**
+         * Sets the custom data associated with this session.
+         *
+         * @param data The session data, may be {@code null}.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setData(SessionData data);
+
+        /**
+         * Sets the cache the repository system may use to save data for 
future reuse during the session.
+         *
+         * @param cache The repository cache, may be {@code null} if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder setCache(RepositoryCache cache);
+
+        /**
+         * Shortcut method to set up local repository manager.
+         *
+         * @param basedir The local repository base directory, may be {@code 
null} if none.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder withLocalRepository(File basedir);
+
+        /**
+         * Shortcut method to shallow-copy passed in session into current 
builder.
+         *
+         * @param session The session to shallow-copy from.
+         * @return This session for chaining, never {@code null}.
+         */
+        SessionBuilder withRepositorySystemSession(RepositorySystemSession 
session);
+
+        /**
+         * Creates a session instance.
+         */
+        CloseableRepositorySystemSession build();
+    }
+
     /**
      * Indicates whether the repository system operates in offline mode and 
avoids/refuses any access to remote
      * repositories.
@@ -283,4 +642,17 @@ public interface RepositorySystemSession {
      * @return The repository cache or {@code null} if none.
      */
     RepositoryCache getCache();
+
+    /**
+     * Registers a handler to execute when this session closed.
+     * <p>
+     * Note: Resolver 1.x sessions will not be able to register handlers. 
Migrate to Resolver 2.x way of handling
+     * sessions to make full use of new features. New features (like HTTP/2 
transport) depend on this functionality.
+     * While they will function with Resolver 1.x sessions, they may produce 
resource leaks.
+     *
+     * @param handler the handler, never {@code null}.
+     * @return {@code true} if handler successfully registered, {@code false} 
otherwise.
+     * @since TBD
+     */
+    boolean addOnSessionEndedHandler(Runnable handler);
 }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java
index b3a0c44e..3048ed7e 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java
@@ -18,6 +18,8 @@
  */
 package org.eclipse.aether.impl;
 
+import 
org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession;
+
 /**
  * Lifecycle managing component for repository system.
  *
@@ -39,4 +41,33 @@ public interface RepositorySystemLifecycle {
      * Throws if repository system is already shut down.
      */
     void addOnSystemEndedHandler(Runnable handler);
+
+    /**
+     * Registers the session for lifecycle tracking: it marks that the passed 
in session instance is about to start.
+     * <p>
+     * <em>Same session instance can be started only once.</em>
+     *
+     * @since TBD
+     */
+    void sessionStarted(CloseableRepositorySystemSession session);
+
+    /**
+     * Signals that passed in session was ended, it will not be used anymore. 
Repository system
+     * will invoke the registered handlers for this session, if any. This 
method throws if the passed in session
+     * instance was not passed to method {@link 
#sessionStarted(CloseableRepositorySystemSession)} beforehand.
+     * <p>
+     * <em>Same session instance can be ended only once.</em>
+     *
+     * @since TBD
+     */
+    void sessionEnded(CloseableRepositorySystemSession session);
+
+    /**
+     * Registers an "on session end" handler.
+     * <p>
+     * Throws if session was not passed to {@link 
#sessionStarted(CloseableRepositorySystemSession)} beforehand.
+     *
+     * @since TBD
+     */
+    void addOnSessionEndedHandle(CloseableRepositorySystemSession session, 
Runnable handler);
 }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
index 19c89723..3769bcc5 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
@@ -27,6 +27,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -59,6 +60,7 @@ import org.eclipse.aether.impl.VersionResolver;
 import org.eclipse.aether.installation.InstallRequest;
 import org.eclipse.aether.installation.InstallResult;
 import org.eclipse.aether.installation.InstallationException;
+import org.eclipse.aether.internal.impl.session.DefaultSessionBuilder;
 import org.eclipse.aether.repository.Authentication;
 import org.eclipse.aether.repository.LocalRepository;
 import org.eclipse.aether.repository.LocalRepositoryManager;
@@ -99,6 +101,8 @@ import static java.util.Objects.requireNonNull;
 public class DefaultRepositorySystem implements RepositorySystem {
     private final AtomicBoolean shutdown;
 
+    private final AtomicInteger sessionIdCounter;
+
     private final VersionResolver versionResolver;
 
     private final VersionRangeResolver versionRangeResolver;
@@ -139,6 +143,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             RemoteRepositoryManager remoteRepositoryManager,
             RepositorySystemLifecycle repositorySystemLifecycle) {
         this.shutdown = new AtomicBoolean(false);
+        this.sessionIdCounter = new AtomicInteger(0);
         this.versionResolver = requireNonNull(versionResolver, "version 
resolver cannot be null");
         this.versionRangeResolver = requireNonNull(versionRangeResolver, 
"version range resolver cannot be null");
         this.artifactResolver = requireNonNull(artifactResolver, "artifact 
resolver cannot be null");
@@ -390,9 +395,17 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
 
     @Override
     public void addOnSystemEndedHandler(Runnable handler) {
+        validateSystem();
         repositorySystemLifecycle.addOnSystemEndedHandler(handler);
     }
 
+    @Override
+    public RepositorySystemSession.SessionBuilder createSessionBuilder() {
+        validateSystem();
+        return new DefaultSessionBuilder(
+                this, repositorySystemLifecycle, "id-" + 
sessionIdCounter.incrementAndGet(), null);
+    }
+
     @Override
     public void shutdown() {
         if (shutdown.compareAndSet(false, true)) {
@@ -411,6 +424,10 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
         invalidSession(session.getAuthenticationSelector(), "authentication 
selector");
         invalidSession(session.getArtifactTypeRegistry(), "artifact type 
registry");
         invalidSession(session.getData(), "data");
+        validateSystem();
+    }
+
+    private void validateSystem() {
         if (shutdown.get()) {
             throw new IllegalStateException("repository system is already shut 
down");
         }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java
index 0d462aa8..73a25c7a 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java
@@ -23,10 +23,13 @@ import javax.inject.Named;
 import javax.inject.Singleton;
 
 import java.util.ArrayList;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.aether.MultiRuntimeException;
+import 
org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession;
 import org.eclipse.aether.impl.RepositorySystemLifecycle;
 
 import static java.util.Objects.requireNonNull;
@@ -41,16 +44,32 @@ public class DefaultRepositorySystemLifecycle implements 
RepositorySystemLifecyc
 
     private final CopyOnWriteArrayList<Runnable> onSystemEndedHandlers;
 
+    private final ConcurrentHashMap<String, CopyOnWriteArrayList<Runnable>> 
onSessionEndedHandlers;
+
     @Inject
     public DefaultRepositorySystemLifecycle() {
         this.shutdown = new AtomicBoolean(false);
         this.onSystemEndedHandlers = new CopyOnWriteArrayList<>();
+        this.onSessionEndedHandlers = new ConcurrentHashMap<>();
     }
 
     @Override
     public void systemEnded() {
+        final ArrayList<Exception> exceptions = new ArrayList<>();
         if (shutdown.compareAndSet(false, true)) {
-            final ArrayList<Exception> exceptions = new ArrayList<>();
+            for (Map.Entry<String, CopyOnWriteArrayList<Runnable>> 
sessionEndedHandlers :
+                    onSessionEndedHandlers.entrySet()) {
+                IllegalStateException sessionNotClosed =
+                        new IllegalStateException("Session " + 
sessionEndedHandlers.getKey() + " not closed");
+                exceptions.add(sessionNotClosed);
+                for (Runnable onCloseHandler : 
sessionEndedHandlers.getValue()) {
+                    try {
+                        onCloseHandler.run();
+                    } catch (Exception e) {
+                        sessionNotClosed.addSuppressed(e);
+                    }
+                }
+            }
             for (Runnable onCloseHandler : onSystemEndedHandlers) {
                 try {
                     onCloseHandler.run();
@@ -69,6 +88,59 @@ public class DefaultRepositorySystemLifecycle implements 
RepositorySystemLifecyc
         onSystemEndedHandlers.add(0, handler);
     }
 
+    @Override
+    public void sessionStarted(CloseableRepositorySystemSession session) {
+        requireNonNull(session, "session cannot be null");
+        requireNotShutdown();
+        String sessionId = session.sessionId();
+        onSessionEndedHandlers.compute(sessionId, (k, v) -> {
+            if (v != null) {
+                throw new IllegalStateException("session instance already 
registered");
+            }
+            return new CopyOnWriteArrayList<>();
+        });
+    }
+
+    @Override
+    public void sessionEnded(CloseableRepositorySystemSession session) {
+        requireNonNull(session, "session cannot be null");
+        requireNotShutdown();
+        String sessionId = session.sessionId();
+        ArrayList<Runnable> handlers = new ArrayList<>();
+        onSessionEndedHandlers.compute(sessionId, (k, v) -> {
+            if (v == null) {
+                throw new IllegalStateException("session instance not 
registered");
+            }
+            handlers.addAll(v);
+            return null;
+        });
+
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (Runnable handler : handlers) {
+            try {
+                handler.run();
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+        }
+        MultiRuntimeException.mayThrow("sessionEnded handler issue(s)", 
exceptions);
+    }
+
+    @Override
+    public void addOnSessionEndedHandle(CloseableRepositorySystemSession 
session, Runnable handler) {
+        requireNonNull(session, "session cannot be null");
+        requireNonNull(handler, "handler cannot be null");
+        requireNotShutdown();
+        String sessionId = session.sessionId();
+        onSessionEndedHandlers.compute(sessionId, (k, v) -> {
+            if (v == null) {
+                throw new IllegalStateException("session instance not 
registered");
+            }
+            v.add(handler);
+            return v;
+        });
+    }
+
     private void requireNotShutdown() {
         if (shutdown.get()) {
             throw new IllegalStateException("repository system is already shut 
down");
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java
new file mode 100644
index 00000000..a9bc520d
--- /dev/null
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java
@@ -0,0 +1,343 @@
+/*
+ * 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.eclipse.aether.internal.impl.session;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.aether.RepositoryCache;
+import org.eclipse.aether.RepositoryListener;
+import org.eclipse.aether.RepositorySystem;
+import 
org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession;
+import org.eclipse.aether.SessionData;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
+import org.eclipse.aether.collection.DependencyManager;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.collection.DependencyTraverser;
+import org.eclipse.aether.collection.VersionFilter;
+import org.eclipse.aether.impl.RepositorySystemLifecycle;
+import org.eclipse.aether.repository.AuthenticationSelector;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.MirrorSelector;
+import org.eclipse.aether.repository.ProxySelector;
+import org.eclipse.aether.repository.WorkspaceReader;
+import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
+import org.eclipse.aether.resolution.ResolutionErrorPolicy;
+import org.eclipse.aether.transfer.TransferListener;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A default implementation of repository system session that is immutable.
+ */
+public final class DefaultCloseableRepositorySystemSession implements 
CloseableRepositorySystemSession {
+    private final String sessionId;
+
+    private final AtomicBoolean closed;
+
+    private final boolean offline;
+
+    private final boolean ignoreArtifactDescriptorRepositories;
+
+    private final ResolutionErrorPolicy resolutionErrorPolicy;
+
+    private final ArtifactDescriptorPolicy artifactDescriptorPolicy;
+
+    private final String checksumPolicy;
+
+    private final String artifactUpdatePolicy;
+
+    private final String metadataUpdatePolicy;
+
+    private final LocalRepositoryManager localRepositoryManager;
+
+    private final WorkspaceReader workspaceReader;
+
+    private final RepositoryListener repositoryListener;
+
+    private final TransferListener transferListener;
+
+    private final Map<String, String> systemProperties;
+
+    private final Map<String, String> userProperties;
+
+    private final Map<String, Object> configProperties;
+
+    private final MirrorSelector mirrorSelector;
+
+    private final ProxySelector proxySelector;
+
+    private final AuthenticationSelector authenticationSelector;
+
+    private final ArtifactTypeRegistry artifactTypeRegistry;
+
+    private final DependencyTraverser dependencyTraverser;
+
+    private final DependencyManager dependencyManager;
+
+    private final DependencySelector dependencySelector;
+
+    private final VersionFilter versionFilter;
+
+    private final DependencyGraphTransformer dependencyGraphTransformer;
+
+    private final SessionData data;
+
+    private final RepositoryCache cache;
+
+    private final RepositorySystem repositorySystem;
+
+    private final RepositorySystemLifecycle repositorySystemLifecycle;
+
+    @SuppressWarnings("checkstyle:parameternumber")
+    public DefaultCloseableRepositorySystemSession(
+            String sessionId,
+            AtomicBoolean closed,
+            boolean offline,
+            boolean ignoreArtifactDescriptorRepositories,
+            ResolutionErrorPolicy resolutionErrorPolicy,
+            ArtifactDescriptorPolicy artifactDescriptorPolicy,
+            String checksumPolicy,
+            String artifactUpdatePolicy,
+            String metadataUpdatePolicy,
+            LocalRepositoryManager localRepositoryManager,
+            WorkspaceReader workspaceReader,
+            RepositoryListener repositoryListener,
+            TransferListener transferListener,
+            Map<String, String> systemProperties,
+            Map<String, String> userProperties,
+            Map<String, Object> configProperties,
+            MirrorSelector mirrorSelector,
+            ProxySelector proxySelector,
+            AuthenticationSelector authenticationSelector,
+            ArtifactTypeRegistry artifactTypeRegistry,
+            DependencyTraverser dependencyTraverser,
+            DependencyManager dependencyManager,
+            DependencySelector dependencySelector,
+            VersionFilter versionFilter,
+            DependencyGraphTransformer dependencyGraphTransformer,
+            SessionData data,
+            RepositoryCache cache,
+            RepositorySystem repositorySystem,
+            RepositorySystemLifecycle repositorySystemLifecycle) {
+        this.sessionId = requireNonNull(sessionId);
+        this.closed = closed == null ? new AtomicBoolean(false) : closed;
+        this.offline = offline;
+        this.ignoreArtifactDescriptorRepositories = 
ignoreArtifactDescriptorRepositories;
+        this.resolutionErrorPolicy = resolutionErrorPolicy;
+        this.artifactDescriptorPolicy = artifactDescriptorPolicy;
+        this.checksumPolicy = checksumPolicy;
+        this.artifactUpdatePolicy = artifactUpdatePolicy;
+        this.metadataUpdatePolicy = metadataUpdatePolicy;
+        this.localRepositoryManager = requireNonNull(localRepositoryManager);
+        this.workspaceReader = workspaceReader;
+        this.repositoryListener = repositoryListener;
+        this.transferListener = transferListener;
+        this.systemProperties = Collections.unmodifiableMap(systemProperties);
+        this.userProperties = Collections.unmodifiableMap(userProperties);
+        this.configProperties = Collections.unmodifiableMap(configProperties);
+        this.mirrorSelector = requireNonNull(mirrorSelector);
+        this.proxySelector = requireNonNull(proxySelector);
+        this.authenticationSelector = requireNonNull(authenticationSelector);
+        this.artifactTypeRegistry = requireNonNull(artifactTypeRegistry);
+        this.dependencyTraverser = dependencyTraverser;
+        this.dependencyManager = dependencyManager;
+        this.dependencySelector = dependencySelector;
+        this.versionFilter = versionFilter;
+        this.dependencyGraphTransformer = dependencyGraphTransformer;
+        this.data = requireNonNull(data);
+        this.cache = cache;
+
+        this.repositorySystem = requireNonNull(repositorySystem);
+        this.repositorySystemLifecycle = 
requireNonNull(repositorySystemLifecycle);
+
+        if (closed == null) {
+            repositorySystemLifecycle.sessionStarted(this);
+        }
+    }
+
+    @Override
+    public String sessionId() {
+        return sessionId;
+    }
+
+    @Override
+    public SessionBuilder copy() {
+        return new DefaultSessionBuilder(repositorySystem, 
repositorySystemLifecycle, sessionId, closed)
+                .withRepositorySystemSession(this);
+    }
+
+    @Override
+    public boolean isOffline() {
+        return offline;
+    }
+
+    @Override
+    public boolean isIgnoreArtifactDescriptorRepositories() {
+        return ignoreArtifactDescriptorRepositories;
+    }
+
+    @Override
+    public ResolutionErrorPolicy getResolutionErrorPolicy() {
+        return resolutionErrorPolicy;
+    }
+
+    @Override
+    public ArtifactDescriptorPolicy getArtifactDescriptorPolicy() {
+        return artifactDescriptorPolicy;
+    }
+
+    @Override
+    public String getChecksumPolicy() {
+        return checksumPolicy;
+    }
+
+    @Override
+    public String getUpdatePolicy() {
+        return getArtifactUpdatePolicy();
+    }
+
+    @Override
+    public String getArtifactUpdatePolicy() {
+        return artifactUpdatePolicy;
+    }
+
+    @Override
+    public String getMetadataUpdatePolicy() {
+        return metadataUpdatePolicy;
+    }
+
+    @Override
+    public LocalRepository getLocalRepository() {
+        return getLocalRepositoryManager().getRepository();
+    }
+
+    @Override
+    public LocalRepositoryManager getLocalRepositoryManager() {
+        return localRepositoryManager;
+    }
+
+    @Override
+    public WorkspaceReader getWorkspaceReader() {
+        return workspaceReader;
+    }
+
+    @Override
+    public RepositoryListener getRepositoryListener() {
+        return repositoryListener;
+    }
+
+    @Override
+    public TransferListener getTransferListener() {
+        return transferListener;
+    }
+
+    @Override
+    public Map<String, String> getSystemProperties() {
+        return systemProperties;
+    }
+
+    @Override
+    public Map<String, String> getUserProperties() {
+        return userProperties;
+    }
+
+    @Override
+    public Map<String, Object> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public MirrorSelector getMirrorSelector() {
+        return mirrorSelector;
+    }
+
+    @Override
+    public ProxySelector getProxySelector() {
+        return proxySelector;
+    }
+
+    @Override
+    public AuthenticationSelector getAuthenticationSelector() {
+        return authenticationSelector;
+    }
+
+    @Override
+    public ArtifactTypeRegistry getArtifactTypeRegistry() {
+        return artifactTypeRegistry;
+    }
+
+    @Override
+    public DependencyTraverser getDependencyTraverser() {
+        return dependencyTraverser;
+    }
+
+    @Override
+    public DependencyManager getDependencyManager() {
+        return dependencyManager;
+    }
+
+    @Override
+    public DependencySelector getDependencySelector() {
+        return dependencySelector;
+    }
+
+    @Override
+    public VersionFilter getVersionFilter() {
+        return versionFilter;
+    }
+
+    @Override
+    public DependencyGraphTransformer getDependencyGraphTransformer() {
+        return dependencyGraphTransformer;
+    }
+
+    @Override
+    public SessionData getData() {
+        return data;
+    }
+
+    @Override
+    public RepositoryCache getCache() {
+        return cache;
+    }
+
+    @Override
+    public boolean addOnSessionEndedHandler(Runnable handler) {
+        throwIfClosed();
+        repositorySystemLifecycle.addOnSessionEndedHandle(this, handler);
+        return true;
+    }
+
+    @Override
+    public void close() {
+        if (closed.compareAndSet(false, true)) {
+            repositorySystemLifecycle.sessionEnded(this);
+        }
+    }
+
+    private void throwIfClosed() {
+        if (closed.get()) {
+            throw new IllegalStateException("Session " + sessionId + " already 
closed");
+        }
+    }
+}
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java
new file mode 100644
index 00000000..2077d87e
--- /dev/null
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java
@@ -0,0 +1,580 @@
+/*
+ * 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.eclipse.aether.internal.impl.session;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.aether.DefaultSessionData;
+import org.eclipse.aether.RepositoryCache;
+import org.eclipse.aether.RepositoryListener;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
+import org.eclipse.aether.SessionData;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
+import org.eclipse.aether.collection.DependencyManager;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.collection.DependencyTraverser;
+import org.eclipse.aether.collection.VersionFilter;
+import org.eclipse.aether.impl.RepositorySystemLifecycle;
+import org.eclipse.aether.repository.AuthenticationSelector;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.MirrorSelector;
+import org.eclipse.aether.repository.ProxySelector;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.WorkspaceReader;
+import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
+import org.eclipse.aether.resolution.ResolutionErrorPolicy;
+import org.eclipse.aether.transfer.TransferListener;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A default implementation of session builder.
+ * <p>
+ * Note: while this class implements {@link RepositorySystemSession}, it 
should NOT be used as such, it is just
+ * an internal technical detail to allow this class some more functional 
helper abilities, like
+ * {@link #withLocalRepository(File)} method is, where "chicken or egg" 
situation would be present. This class is
+ * NOT immutable nor thread safe.
+ */
+public final class DefaultSessionBuilder implements SessionBuilder, 
RepositorySystemSession {
+    private static final MirrorSelector NULL_MIRROR_SELECTOR = r -> null;
+
+    private static final ProxySelector NULL_PROXY_SELECTOR = 
RemoteRepository::getProxy;
+
+    private static final AuthenticationSelector NULL_AUTHENTICATION_SELECTOR = 
RemoteRepository::getAuthentication;
+
+    private static final ArtifactTypeRegistry NULL_ARTIFACT_TYPE_REGISTRY = t 
-> null;
+
+    private final RepositorySystem repositorySystem;
+
+    private final RepositorySystemLifecycle repositorySystemLifecycle;
+
+    private final String sessionId;
+
+    private final AtomicBoolean closed;
+
+    private final ArrayList<Runnable> onCloseHandler;
+
+    private boolean offline;
+
+    private boolean ignoreArtifactDescriptorRepositories;
+
+    private ResolutionErrorPolicy resolutionErrorPolicy;
+
+    private ArtifactDescriptorPolicy artifactDescriptorPolicy;
+
+    private String checksumPolicy;
+
+    private String artifactUpdatePolicy;
+
+    private String metadataUpdatePolicy;
+
+    private LocalRepositoryManager localRepositoryManager;
+
+    private WorkspaceReader workspaceReader;
+
+    private RepositoryListener repositoryListener;
+
+    private TransferListener transferListener;
+
+    private Map<String, String> systemProperties = new HashMap<>();
+
+    private Map<String, String> userProperties = new HashMap<>();
+
+    private Map<String, Object> configProperties = new HashMap<>();
+
+    private MirrorSelector mirrorSelector = NULL_MIRROR_SELECTOR;
+
+    private ProxySelector proxySelector = NULL_PROXY_SELECTOR;
+
+    private AuthenticationSelector authenticationSelector = 
NULL_AUTHENTICATION_SELECTOR;
+
+    private ArtifactTypeRegistry artifactTypeRegistry = 
NULL_ARTIFACT_TYPE_REGISTRY;
+
+    private DependencyTraverser dependencyTraverser;
+
+    private DependencyManager dependencyManager;
+
+    private DependencySelector dependencySelector;
+
+    private VersionFilter versionFilter;
+
+    private DependencyGraphTransformer dependencyGraphTransformer;
+
+    private SessionData data = new DefaultSessionData();
+
+    private RepositoryCache cache;
+
+    public DefaultSessionBuilder(
+            RepositorySystem repositorySystem,
+            RepositorySystemLifecycle repositorySystemLifecycle,
+            String sessionId,
+            AtomicBoolean closed) {
+        this.repositorySystem = requireNonNull(repositorySystem);
+        this.repositorySystemLifecycle = 
requireNonNull(repositorySystemLifecycle);
+        this.sessionId = requireNonNull(sessionId);
+        this.closed = closed;
+        this.onCloseHandler = new ArrayList<>();
+    }
+
+    @Override
+    public boolean isOffline() {
+        return offline;
+    }
+
+    @Override
+    public boolean isIgnoreArtifactDescriptorRepositories() {
+        return ignoreArtifactDescriptorRepositories;
+    }
+
+    @Override
+    public ResolutionErrorPolicy getResolutionErrorPolicy() {
+        return resolutionErrorPolicy;
+    }
+
+    @Override
+    public ArtifactDescriptorPolicy getArtifactDescriptorPolicy() {
+        return artifactDescriptorPolicy;
+    }
+
+    @Override
+    public String getChecksumPolicy() {
+        return checksumPolicy;
+    }
+
+    @Override
+    public String getUpdatePolicy() {
+        return getArtifactUpdatePolicy();
+    }
+
+    @Override
+    public String getArtifactUpdatePolicy() {
+        return artifactUpdatePolicy;
+    }
+
+    @Override
+    public String getMetadataUpdatePolicy() {
+        return metadataUpdatePolicy;
+    }
+
+    @Override
+    public LocalRepository getLocalRepository() {
+        return localRepositoryManager.getRepository();
+    }
+
+    @Override
+    public LocalRepositoryManager getLocalRepositoryManager() {
+        return localRepositoryManager;
+    }
+
+    @Override
+    public WorkspaceReader getWorkspaceReader() {
+        return workspaceReader;
+    }
+
+    @Override
+    public RepositoryListener getRepositoryListener() {
+        return repositoryListener;
+    }
+
+    @Override
+    public TransferListener getTransferListener() {
+        return transferListener;
+    }
+
+    @Override
+    public Map<String, String> getSystemProperties() {
+        return systemProperties;
+    }
+
+    @Override
+    public Map<String, String> getUserProperties() {
+        return userProperties;
+    }
+
+    @Override
+    public Map<String, Object> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public MirrorSelector getMirrorSelector() {
+        return mirrorSelector;
+    }
+
+    @Override
+    public ProxySelector getProxySelector() {
+        return proxySelector;
+    }
+
+    @Override
+    public AuthenticationSelector getAuthenticationSelector() {
+        return authenticationSelector;
+    }
+
+    @Override
+    public ArtifactTypeRegistry getArtifactTypeRegistry() {
+        return artifactTypeRegistry;
+    }
+
+    @Override
+    public DependencyTraverser getDependencyTraverser() {
+        return dependencyTraverser;
+    }
+
+    @Override
+    public DependencyManager getDependencyManager() {
+        return dependencyManager;
+    }
+
+    @Override
+    public DependencySelector getDependencySelector() {
+        return dependencySelector;
+    }
+
+    @Override
+    public VersionFilter getVersionFilter() {
+        return versionFilter;
+    }
+
+    @Override
+    public DependencyGraphTransformer getDependencyGraphTransformer() {
+        return dependencyGraphTransformer;
+    }
+
+    @Override
+    public SessionData getData() {
+        return data;
+    }
+
+    @Override
+    public RepositoryCache getCache() {
+        return cache;
+    }
+
+    @Override
+    public boolean addOnSessionEndedHandler(Runnable handler) {
+        requireNonNull(handler, "null handler");
+        onCloseHandler.add(handler);
+        return true;
+    }
+
+    @Override
+    public DefaultSessionBuilder setOffline(boolean offline) {
+        this.offline = offline;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder 
setIgnoreArtifactDescriptorRepositories(boolean 
ignoreArtifactDescriptorRepositories) {
+        this.ignoreArtifactDescriptorRepositories = 
ignoreArtifactDescriptorRepositories;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder 
setResolutionErrorPolicy(ResolutionErrorPolicy resolutionErrorPolicy) {
+        this.resolutionErrorPolicy = resolutionErrorPolicy;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder 
setArtifactDescriptorPolicy(ArtifactDescriptorPolicy artifactDescriptorPolicy) {
+        this.artifactDescriptorPolicy = artifactDescriptorPolicy;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setChecksumPolicy(String checksumPolicy) {
+        this.checksumPolicy = checksumPolicy;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setUpdatePolicy(String updatePolicy) {
+        setArtifactUpdatePolicy(updatePolicy);
+        setMetadataUpdatePolicy(updatePolicy);
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setArtifactUpdatePolicy(String 
artifactUpdatePolicy) {
+        this.artifactUpdatePolicy = artifactUpdatePolicy;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setMetadataUpdatePolicy(String 
metadataUpdatePolicy) {
+        this.metadataUpdatePolicy = metadataUpdatePolicy;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder 
setLocalRepositoryManager(LocalRepositoryManager localRepositoryManager) {
+        this.localRepositoryManager = localRepositoryManager;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setWorkspaceReader(WorkspaceReader 
workspaceReader) {
+        this.workspaceReader = workspaceReader;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setRepositoryListener(RepositoryListener 
repositoryListener) {
+        this.repositoryListener = repositoryListener;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setTransferListener(TransferListener 
transferListener) {
+        this.transferListener = transferListener;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setSystemProperties(Map<?, ?> 
systemProperties) {
+        this.systemProperties = copySafe(systemProperties, String.class);
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setSystemProperty(String key, String value) {
+        if (value != null) {
+            systemProperties.put(key, value);
+        } else {
+            systemProperties.remove(key);
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setUserProperties(Map<?, ?> userProperties) {
+        this.userProperties = copySafe(userProperties, String.class);
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setUserProperty(String key, String value) {
+        if (value != null) {
+            userProperties.put(key, value);
+        } else {
+            userProperties.remove(key);
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setConfigProperties(Map<?, ?> 
configProperties) {
+        this.configProperties = copySafe(configProperties, Object.class);
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setConfigProperty(String key, Object value) {
+        if (value != null) {
+            configProperties.put(key, value);
+        } else {
+            configProperties.remove(key);
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setMirrorSelector(MirrorSelector 
mirrorSelector) {
+        this.mirrorSelector = mirrorSelector;
+        if (this.mirrorSelector == null) {
+            this.mirrorSelector = NULL_MIRROR_SELECTOR;
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setProxySelector(ProxySelector proxySelector) 
{
+        this.proxySelector = proxySelector;
+        if (this.proxySelector == null) {
+            this.proxySelector = NULL_PROXY_SELECTOR;
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder 
setAuthenticationSelector(AuthenticationSelector authenticationSelector) {
+        this.authenticationSelector = authenticationSelector;
+        if (this.authenticationSelector == null) {
+            this.authenticationSelector = NULL_AUTHENTICATION_SELECTOR;
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setArtifactTypeRegistry(ArtifactTypeRegistry 
artifactTypeRegistry) {
+        this.artifactTypeRegistry = artifactTypeRegistry;
+        if (this.artifactTypeRegistry == null) {
+            this.artifactTypeRegistry = NULL_ARTIFACT_TYPE_REGISTRY;
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setDependencyTraverser(DependencyTraverser 
dependencyTraverser) {
+        this.dependencyTraverser = dependencyTraverser;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setDependencyManager(DependencyManager 
dependencyManager) {
+        this.dependencyManager = dependencyManager;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setDependencySelector(DependencySelector 
dependencySelector) {
+        this.dependencySelector = dependencySelector;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setVersionFilter(VersionFilter versionFilter) 
{
+        this.versionFilter = versionFilter;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder 
setDependencyGraphTransformer(DependencyGraphTransformer 
dependencyGraphTransformer) {
+        this.dependencyGraphTransformer = dependencyGraphTransformer;
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setData(SessionData data) {
+        this.data = data;
+        if (this.data == null) {
+            this.data = new DefaultSessionData();
+        }
+        return this;
+    }
+
+    @Override
+    public DefaultSessionBuilder setCache(RepositoryCache cache) {
+        this.cache = cache;
+        return this;
+    }
+
+    @Override
+    public SessionBuilder withLocalRepository(File basedir) {
+        LocalRepository localRepository = new LocalRepository(basedir, 
"default");
+        this.localRepositoryManager = 
repositorySystem.newLocalRepositoryManager(this, localRepository);
+        return this;
+    }
+
+    @Override
+    public SessionBuilder withRepositorySystemSession(RepositorySystemSession 
session) {
+        requireNonNull(session, "repository system session cannot be null");
+        setOffline(session.isOffline());
+        
setIgnoreArtifactDescriptorRepositories(session.isIgnoreArtifactDescriptorRepositories());
+        setResolutionErrorPolicy(session.getResolutionErrorPolicy());
+        setArtifactDescriptorPolicy(session.getArtifactDescriptorPolicy());
+        setChecksumPolicy(session.getChecksumPolicy());
+        setUpdatePolicy(session.getUpdatePolicy());
+        setMetadataUpdatePolicy(session.getMetadataUpdatePolicy());
+        setLocalRepositoryManager(session.getLocalRepositoryManager());
+        setWorkspaceReader(session.getWorkspaceReader());
+        setRepositoryListener(session.getRepositoryListener());
+        setTransferListener(session.getTransferListener());
+        setSystemProperties(session.getSystemProperties());
+        setUserProperties(session.getUserProperties());
+        setConfigProperties(session.getConfigProperties());
+        setMirrorSelector(session.getMirrorSelector());
+        setProxySelector(session.getProxySelector());
+        setAuthenticationSelector(session.getAuthenticationSelector());
+        setArtifactTypeRegistry(session.getArtifactTypeRegistry());
+        setDependencyTraverser(session.getDependencyTraverser());
+        setDependencyManager(session.getDependencyManager());
+        setDependencySelector(session.getDependencySelector());
+        setVersionFilter(session.getVersionFilter());
+        setDependencyGraphTransformer(session.getDependencyGraphTransformer());
+        setData(session.getData());
+        setCache(session.getCache());
+        return this;
+    }
+
+    @Override
+    public CloseableRepositorySystemSession build() {
+        CloseableRepositorySystemSession result = new 
DefaultCloseableRepositorySystemSession(
+                sessionId,
+                closed,
+                offline,
+                ignoreArtifactDescriptorRepositories,
+                resolutionErrorPolicy,
+                artifactDescriptorPolicy,
+                checksumPolicy,
+                artifactUpdatePolicy,
+                metadataUpdatePolicy,
+                localRepositoryManager,
+                workspaceReader,
+                repositoryListener,
+                transferListener,
+                systemProperties,
+                userProperties,
+                configProperties,
+                mirrorSelector,
+                proxySelector,
+                authenticationSelector,
+                artifactTypeRegistry,
+                dependencyTraverser,
+                dependencyManager,
+                dependencySelector,
+                versionFilter,
+                dependencyGraphTransformer,
+                data,
+                cache,
+                repositorySystem,
+                repositorySystemLifecycle);
+        onCloseHandler.forEach(result::addOnSessionEndedHandler);
+        return result;
+    }
+
+    @SuppressWarnings("checkstyle:magicnumber")
+    private static <T> Map<String, T> copySafe(Map<?, ?> table, Class<T> 
valueType) {
+        Map<String, T> map;
+        if (table == null || table.isEmpty()) {
+            map = new HashMap<>();
+        } else {
+            map = new HashMap<>((int) (table.size() / 0.75f) + 1);
+            for (Map.Entry<?, ?> entry : table.entrySet()) {
+                Object key = entry.getKey();
+                if (key instanceof String) {
+                    Object value = entry.getValue();
+                    if (valueType.isInstance(value)) {
+                        map.put(key.toString(), valueType.cast(value));
+                    }
+                }
+            }
+        }
+        return map;
+    }
+}
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
index b8f544d8..49e2bcf4 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
@@ -40,6 +40,10 @@
   </properties>
 
   <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven.resolver</groupId>
       <artifactId>maven-resolver-api</artifactId>
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
index 35d97f29..423aff85 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
@@ -43,6 +43,10 @@
   </properties>
 
   <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven.resolver</groupId>
       <artifactId>maven-resolver-api</artifactId>
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
index 9e0b6dad..f71b38e2 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
@@ -43,6 +43,10 @@
   </properties>
 
   <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven.resolver</groupId>
       <artifactId>maven-resolver-api</artifactId>
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java
 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java
index 51b8b9f3..4dd30d8c 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java
@@ -26,6 +26,8 @@ import org.eclipse.aether.ConfigurationProperties;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.util.ConfigUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * JDK Transport customizer.
@@ -33,6 +35,8 @@ import org.eclipse.aether.util.ConfigUtils;
  * @since TBD
  */
 final class JdkHttpTransporterCustomizer {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(JdkHttpTransporterCustomizer.class);
+
     private JdkHttpTransporterCustomizer() {}
 
     static void customizeBuilder(
@@ -44,7 +48,9 @@ final class JdkHttpTransporterCustomizer {
     }
 
     static void customizeHttpClient(RepositorySystemSession session, 
RemoteRepository repository, HttpClient client) {
-        // TODO: register client.close(); once onSessionClose feature present
+        if (!session.addOnSessionEndedHandler(client::close)) {
+            LOGGER.warn("Using Resolver 2 feature without Resolver 2 session 
handling, you may leak resources.");
+        }
     }
 
     /**
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml
index ff084748..ab221e91 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml
@@ -38,6 +38,10 @@
   </properties>
 
   <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven.resolver</groupId>
       <artifactId>maven-resolver-api</artifactId>
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
index 80dd1d65..81b8c34f 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
@@ -25,6 +25,8 @@ import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.spi.connector.transport.Transporter;
 import org.eclipse.aether.spi.connector.transport.TransporterFactory;
 import org.eclipse.aether.transfer.NoTransporterException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static java.util.Objects.requireNonNull;
 
@@ -37,6 +39,8 @@ import static java.util.Objects.requireNonNull;
 public final class JdkTransporterFactory implements TransporterFactory {
     public static final String NAME = "jdk";
 
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(JdkTransporterFactory.class);
+
     private float priority = Float.MIN_VALUE;
 
     @Override
@@ -55,6 +59,7 @@ public final class JdkTransporterFactory implements 
TransporterFactory {
         requireNonNull(session, "session cannot be null");
         requireNonNull(repository, "repository cannot be null");
 
+        LOGGER.debug("Needs Java11+ to function");
         throw new NoTransporterException(repository, "JDK Transport needs 
Java11+");
     }
 }
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml
index b90c1cb9..3d66c163 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml
@@ -65,6 +65,10 @@
     </dependency>
 
     <!-- Real dependencies -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven.resolver</groupId>
       <artifactId>maven-resolver-api</artifactId>
diff --git a/maven-resolver-transport-jetty/pom.xml 
b/maven-resolver-transport-jetty/pom.xml
index e9c2b630..3243d082 100644
--- a/maven-resolver-transport-jetty/pom.xml
+++ b/maven-resolver-transport-jetty/pom.xml
@@ -41,6 +41,10 @@
   </properties>
 
   <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven.resolver</groupId>
       <artifactId>maven-resolver-api</artifactId>
diff --git 
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
 
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
index 56f8d93e..de71e68d 100644
--- 
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
+++ 
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
@@ -62,6 +62,8 @@ import org.eclipse.jetty.http2.client.HTTP2Client;
 import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
 import org.eclipse.jetty.io.ClientConnector;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A transporter for HTTP/HTTPS.
@@ -346,6 +348,8 @@ final class JettyTransporter extends AbstractTransporter {
      */
     static final String JETTY_INSTANCE_KEY_PREFIX = 
JettyTransporterFactory.class.getName() + ".jetty.";
 
+    static final Logger LOGGER = 
LoggerFactory.getLogger(JettyTransporter.class);
+
     @SuppressWarnings("checkstyle:methodlength")
     private static HttpClient getOrCreateClient(RepositorySystemSession 
session, RemoteRepository repository)
             throws NoTransporterException {
@@ -431,6 +435,16 @@ final class JettyTransporter extends AbstractTransporter {
                             }
                         }
                     }
+                    if (!session.addOnSessionEndedHandler(() -> {
+                        try {
+                            httpClient.stop();
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    })) {
+                        LOGGER.warn(
+                                "Using Resolver 2 feature without Resolver 2 
session handling, you may leak resources.");
+                    }
                     httpClient.start();
                     return httpClient;
                 } catch (Exception e) {
diff --git a/src/site/markdown/upgrading-resolver.md 
b/src/site/markdown/upgrading-resolver.md
index 454bbbef..c1a1e899 100644
--- a/src/site/markdown/upgrading-resolver.md
+++ b/src/site/markdown/upgrading-resolver.md
@@ -24,3 +24,27 @@ another major version of Resolver.
 Maven Resolver upcoming major version 2.x should be "smooth sailing", as long 
you
 do not depend (directly or indirectly) on **deprecated** classes from Resolver
 1.x line. Always use latest 1.x release to check for deprecated classes.
+
+## Session handling changes
+
+Maven Resolver 2.x introduced "onSessionEnd" hooks, that became required for
+some of the new features (like HTTP/2 transports are). While existing 
"Resolver 1.x"
+way of handling session will still work, it may produce resource leaks.
+Client code **managing Resolver** (like Maven) is strongly advised to upgrade
+session handling. Client code **using Resolver** (like Maven Mojos) 
+do not have to change anything, they should be able to continue to 
+function in very same way as before (as with Resolver 1.x).
+
+What changed on surface:
+* introduction of `RepositorySystemSession` nested interfaces 
`CloseableRepositorySystemSession` and `SessionBuilder`.
+* introduction of `RepositorySystem` new method `createSessionBuilder` that 
creates `SessionBuilder` instances.
+* deprecation of `DefaultRepositorySystemSession` default constructor, this 
constructor is actually the "Resolver 1.x way" of using sessions.
+
+Required changes in **client code managing Resolver 2.x**:
+* do not use `DefaultRepositorySystemSession` default constructor anymore.
+* instead, use `RepositorySystem#createSessionBuilder` to create 
`SessionBuilder` and out of it `CloseableRepositorySystemSession` instances.
+* handle sessions as resources: each created instance should be closed once 
finished their use.
+* session instances created by given `RepositorySystem` should be used only 
with that same instance.
+* to shallow-copy session instances (for alteration purposes) using existing 
`DefaultRepositorySystemSession` copy constructor is acceptable (this is what 
Mojos do).
+* to shallow-copy session instances (for alteration purposes) there is 
`CloseableRepositorySystemSession#copy` method as well, if closeable session is 
needed.
+* to shallow-copy session instances but have new lifecycle as well, use 
`SessionBuilder#withRepositorySystemSession` on newly created builder instances.

Reply via email to