Repository: hibernate/hibernate-reactive
Pull request #2242 (2025-05-01 04:06:58)

Bump actions/setup-java from 4.7.0 to 4.7.1 in the workflow-actions group
Bumps the workflow-actions group with 1 update: [actions/setup-java](https://github.com/actions/setup-java).

Updates `actions/setup-java` from 4.7.0 to 4.7.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-java/releases">actions/setup-java's releases</a>.</em></p>
<blockquote>
<h2>v4.7.1</h2>
<h2>What's Changed</h2>
<h3>Documentation changes</h3>
<ul>
<li>Add Documentation to Recommend Using GraalVM JDK 17 Version to 17.0.12 to Align with GFTC License Terms by <a href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/704">actions/setup-java#704</a></li>
<li>Remove duplicated GraalVM section in documentation by <a href="https://github.com/Marcono1234"><code>@​Marcono1234</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/716">actions/setup-java#716</a></li>
</ul>
<h3>Dependency updates:</h3>
<ul>
<li>Upgrade <code>@​action/cache</code> from 4.0.0 to 4.0.2 by <a href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/766">actions/setup-java#766</a></li>
<li>Upgrade <code>@​actions/glob</code> from 0.4.0 to 0.5.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/744">actions/setup-java#744</a></li>
<li>Upgrade ts-jest from 29.1.2 to 29.2.5 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/743">actions/setup-java#743</a></li>
<li>Upgrade <code>@​action/cache</code> to 4.0.3 by <a href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/773">actions/setup-java#773</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-java/compare/v4...v4.7.1">https://github.com/actions/setup-java/compare/v4...v4.7.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-java/commit/c5195efecf7bdfc987ee8bae7a71cb8b11521c00"><code>c5195ef</code></a> actions/cache upgrade to 4.0.3 (<a href="https://redirect.github.com/actions/setup-java/issues/773">#773</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/dd38875f930accc291b5816356a21f72056c0b70"><code>dd38875</code></a> Bump ts-jest from 29.1.2 to 29.2.5 (<a href="https://redirect.github.com/actions/setup-java/issues/743">#743</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/148017a9b0c6af80330bcc5db11d1c670d2e7074"><code>148017a</code></a> Bump <code>@​actions/glob</code> from 0.4.0 to 0.5.0 (<a href="https://redirect.github.com/actions/setup-java/issues/744">#744</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/3b6c050358614dd082e53cdbc55580431fc4e437"><code>3b6c050</code></a> Remove duplicated GraalVM section in documentation (<a href="https://redirect.github.com/actions/setup-java/issues/716">#716</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/b8ebb8ba1d9655f7f159c0a8b8135606ae11b5c9"><code>b8ebb8b</code></a> upgrade <code>@​action/cache</code> from 4.0.0 to 4.0.2 (<a href="https://redirect.github.com/actions/setup-java/issues/766">#766</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/799ee7c97e9721ef38d1a7e8486c39753b9d6102"><code>799ee7c</code></a> Add Documentation to Recommend Using GraalVM JDK 17 Version to 17.0.12 to Ali...</li>
<li>See full diff in <a href="https://github.com/actions/setup-java/compare/3a4f6e1af504cf6a31855fa899c6aa5355ba6c12...c5195efecf7bdfc987ee8bae7a71cb8b11521c00">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-java&package-manager=github_actions&previous-version=4.7.0&new-version=4.7.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions


</details>


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2718f729..63f7c776 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -101,13 +101,13 @@ jobs:
           key: gradle-examples-${{ matrix.db }}-${{ steps.get-date.outputs.yearmonth }}
       - name: Set up JDK 11
         if: ${{ startsWith( inputs.branch, 'wip/2' ) }}
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: 'temurin'
           java-version: 11
       - name: Set up JDK 17
         if: ${{ !startsWith( inputs.branch, 'wip/2' ) }}
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: 'temurin'
           java-version: 17
@@ -150,13 +150,13 @@ jobs:
           key: gradle-db-${{ matrix.db }}-${{ steps.get-date.outputs.yearmonth }}
       - name: Set up JDK 11
         if: ${{ startsWith( inputs.branch, 'wip/2' ) }}
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: 'temurin'
           java-version: 11
       - name: Set up JDK 17
         if: ${{ !startsWith( inputs.branch, 'wip/2' ) }}
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
 
         with:
           distribution: 'temurin'
@@ -255,7 +255,7 @@ jobs:
           release: ${{ matrix.java.java_version_numeric }}
       - name: Set up latest JDK ${{ matrix.java.name }} from Adoptium
         if: matrix.java.from == '' || matrix.java.from == 'adoptium.net'
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: 'temurin'
           java-version: ${{ matrix.java.java_version_numeric }}
@@ -265,14 +265,14 @@ jobs:
         run: echo "::set-output name=path::${JAVA_HOME}"
       - name: Set up JDK 11
         if: ${{ startsWith( inputs.branch, 'wip/2' ) }}
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: 'temurin'
           java-version: 11
           check-latest: true
       - name: Set up JDK 17
         if: ${{ !startsWith( inputs.branch, 'wip/2' ) }}
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: 'temurin'
           java-version: 17
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 13d87abe..c5f0ffbc 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -27,7 +27,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Setup Java
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+        uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
         with:
           distribution: temurin
           java-version: 17
diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
index 59a97eee..fea160b2 100644
--- a/ci/snapshot-publish.Jenkinsfile
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -41,19 +41,15 @@ pipeline {
 				script {
 					withCredentials([
 							// https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh
-							// TODO: Once we switch to maven-central publishing (from nexus2) we need to add a new credentials
-							//  to use the following env variable names to set the user/password:
-							//  - JRELEASER_MAVENCENTRAL_USERNAME
-							//  - JRELEASER_MAVENCENTRAL_TOKEN
-							//  Also use the new `credentialsId` for Maven Central, e.g.:
-							//  usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'),
-							usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_USERNAME'),
+							// TODO: Once we switch to maven-central publishing (from nexus2) we need to update credentialsId:
+							//  https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables
+							usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'),
 							gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'),
 							string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN')
 					]) {
 						checkoutReleaseScripts()
 						def version = sh(
-								script: ".release/scripts/determine-current-version.sh orm",
+								script: ".release/scripts/determine-current-version.sh reactive",
 								returnStdout: true
 						).trim()
 						echo "Current version: '${version}'"
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java
index 3dfc8dcd..6d87416b 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java
@@ -37,9 +37,9 @@
 import org.hibernate.persister.collection.CollectionPersister;
 import org.hibernate.persister.entity.EntityPersister;
 import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl;
+import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor;
 import org.hibernate.reactive.logging.impl.Log;
 import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
-import org.hibernate.reactive.session.ReactiveSession;
 import org.hibernate.sql.exec.spi.Callback;
 import org.hibernate.sql.results.graph.entity.EntityInitializer;
 import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState;
@@ -79,8 +79,9 @@ private class NonLazyCollectionInitializer implements Consumer<PersistentCollect
 		@Override
 		public void accept(PersistentCollection<?> nonLazyCollection) {
 			if ( !nonLazyCollection.wasInitialized() ) {
-				stage = stage.thenCompose( v -> ( (ReactiveSession) getSession() )
-						.reactiveInitializeCollection( nonLazyCollection, false ) );
+				stage = stage.thenCompose( v ->
+						( (ReactiveSharedSessionContractImplementor) getSession() )
+								.reactiveInitializeCollection( nonLazyCollection, false ) );
 			}
 		}
 	}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
index 4bc488bb..9ce6a7c5 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
@@ -23,7 +23,6 @@
 import org.hibernate.collection.spi.PersistentCollection;
 import org.hibernate.engine.spi.CollectionEntry;
 import org.hibernate.engine.spi.PersistenceContext;
-import org.hibernate.engine.spi.SessionImplementor;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.metamodel.mapping.PluralAttributeMapping;
 import org.hibernate.persister.collection.CollectionPersister;
@@ -58,7 +57,7 @@ public static CompletionStage<Object> replace(
 			CollectionType type,
 			Object original,
 			Object target,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache,
 			ForeignKeyDirection foreignKeyDirection)
@@ -76,7 +75,7 @@ public static CompletionStage<Object> replace(
 			CollectionType type,
 			Object original,
 			Object target,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache) throws HibernateException {
 		if ( original == null ) {
@@ -96,7 +95,7 @@ else if ( !Hibernate.isInitialized( original ) ) {
 	 */
 	private static Object replaceNullOriginal(
 			Object target,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		if ( target == null ) {
 			return null;
 		}
@@ -110,20 +109,18 @@ else if ( target instanceof Map<?, ?> map ) {
 		}
 		else {
 			final PersistenceContext persistenceContext = session.getPersistenceContext();
-			final PersistentCollection<?> collectionHolder = persistenceContext.getCollectionHolder( target );
-			if ( collectionHolder != null ) {
-				if ( collectionHolder instanceof PersistentArrayHolder<?> arrayHolder ) {
-					persistenceContext.removeCollectionHolder( target );
-					arrayHolder.beginRead();
-					final PluralAttributeMapping attributeMapping =
-							persistenceContext.getCollectionEntry( collectionHolder )
-									.getLoadedPersister().getAttributeMapping();
-					arrayHolder.injectLoadedState( attributeMapping, null );
-					arrayHolder.endRead();
-					arrayHolder.dirty();
-					persistenceContext.addCollectionHolder( collectionHolder );
-					return arrayHolder.getArray();
-				}
+            if ( persistenceContext.getCollectionHolder( target )
+					instanceof PersistentArrayHolder<?> arrayHolder ) {
+				persistenceContext.removeCollectionHolder( target );
+				arrayHolder.beginRead();
+				final PluralAttributeMapping attributeMapping =
+						persistenceContext.getCollectionEntry( arrayHolder )
+								.getLoadedPersister().getAttributeMapping();
+				arrayHolder.injectLoadedState( attributeMapping, null );
+				arrayHolder.endRead();
+				arrayHolder.dirty();
+				persistenceContext.addCollectionHolder( arrayHolder );
+				return arrayHolder.getArray();
 			}
 		}
 		return null;
@@ -134,7 +131,7 @@ private static Object replaceUninitializedOriginal(
 			CollectionType type,
 			Object original,
 			Object target,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Map<Object, Object> copyCache) {
 		final PersistentCollection<?> persistentCollection = (PersistentCollection<?>) original;
 		if ( persistentCollection.hasQueuedOperations() ) {
@@ -142,8 +139,9 @@ private static Object replaceUninitializedOriginal(
 				// A managed entity with an uninitialized collection is being merged,
 				// We need to replace any detached entities in the queued operations
 				// with managed copies.
-				final AbstractPersistentCollection<?> pc = (AbstractPersistentCollection<?>) original;
-				pc.replaceQueuedOperationValues(
+				final AbstractPersistentCollection<?> collection =
+						(AbstractPersistentCollection<?>) original;
+				collection.replaceQueuedOperationValues(
 						session.getFactory()
 								.getMappingMetamodel()
 								.getCollectionDescriptor( type.getRole() ), copyCache
@@ -166,7 +164,7 @@ private static CompletionStage<Object> replaceOriginal(
 			CollectionType type,
 			Object original,
 			Object target,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache) {
 
@@ -182,18 +180,20 @@ private static CompletionStage<Object> replaceOriginal(
 		).thenCompose( result -> {
 			if ( original == target ) {
 				// get the elements back into the target making sure to handle dirty flag
-				final boolean wasClean =
-						target instanceof PersistentCollection<?> collection
-								&& !collection.isDirty();
 				//TODO: this is a little inefficient, don't need to do a whole
 				//      deep replaceElements() call
-				return replaceElements( type, result, target, owner, copyCache, session )
-						.thenApply( unused -> {
-							if ( wasClean ) {
-								( (PersistentCollection<?>) target ).clearDirty();
-							}
-							return target;
-						} );
+				final CompletionStage<Object> replaced =
+						replaceElements( type, result, target, owner, copyCache, session );
+				if ( target instanceof PersistentCollection<?> collection
+						&& !collection.isDirty() ) {
+					return replaced.thenApply( unused -> {
+						collection.clearDirty();
+						return target;
+					} );
+				}
+				else {
+					return replaced.thenApply( unused -> target );
+				}
 			}
 			else {
 				return completedFuture( result );
@@ -210,7 +210,7 @@ private static CompletionStage<Object> replaceElements(
 			Object target,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		if ( type instanceof ArrayType ) {
 			return replaceArrayTypeElements( type, original, target, owner, copyCache, session );
 		}
@@ -220,7 +220,7 @@ else if ( type instanceof CustomCollectionType ) {
 		else if ( type instanceof MapType ) {
 			return replaceMapTypeElements(
 					type,
-					(Map<Object, Object>) original,
+					(Map<?, ?>) original,
 					(Map<Object, Object>) target,
 					owner,
 					copyCache,
@@ -245,55 +245,61 @@ private static CompletionStage<Object> replaceCollectionTypeElements(
 			final Collection<Object> result,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		result.clear();
-
 		// copy elements into newly empty target collection
 		final Type elemType = type.getElementType( session.getFactory() );
-		return loop(
-				(Collection<Object>) original, o -> getReplace( elemType, o, owner, session, copyCache )
-						.thenAccept( result::add )
-		).thenCompose( unused -> {
-			// if the original is a PersistentCollection, and that original
-			// was not flagged as dirty, then reset the target's dirty flag
-			// here after the copy operation.
-			// </p>
-			// One thing to be careful of here is a "bare" original collection
-			// in which case we should never ever ever reset the dirty flag
-			// on the target because we simply do not know...
-			if ( original instanceof PersistentCollection<?> originalPersistentCollection
-					&& result instanceof PersistentCollection<?> resultPersistentCollection ) {
-				return preserveSnapshot(
-						originalPersistentCollection, resultPersistentCollection,
-						elemType, owner, copyCache, session
-				).thenApply( v -> {
-					if ( !originalPersistentCollection.isDirty() ) {
-						resultPersistentCollection.clearDirty();
-					}
-					return result;
-				} );
-			}
-			else {
-				return completedFuture( result );
-			}
-		} );
+        return loop( (Collection<?>) original,
+				o -> getReplace( elemType, o, owner, session, copyCache )
+						.thenAccept( result::add ) )
+				.thenCompose( v -> preserveSnapshotIfNecessary( original, result, owner, copyCache, session, elemType ) );
+	}
+
+	private static CompletionStage<Object> preserveSnapshotIfNecessary(
+			Object original,
+			Collection<Object> result,
+			Object owner,
+			Map<Object, Object> copyCache,
+			SharedSessionContractImplementor session,
+			Type elemType) {
+		// if the original is a PersistentCollection, and that original
+		// was not flagged as dirty, then reset the target's dirty flag
+		// here after the copy operation.
+		// </p>
+		// One thing to be careful of here is a "bare" original collection
+		// in which case we should never ever ever reset the dirty flag
+		// on the target because we simply do not know...
+		if ( original instanceof PersistentCollection<?> originalCollection
+				&& result instanceof PersistentCollection<?> resultCollection ) {
+			return preserveSnapshot(
+					originalCollection, resultCollection,
+					elemType, owner, copyCache, session
+			).thenApply( v -> {
+				if ( !originalCollection.isDirty() ) {
+					resultCollection.clearDirty();
+				}
+				return result;
+			} );
+		}
+		else {
+			return completedFuture( result );
+		}
 	}
 
 	private static CompletionStage<Object> replaceMapTypeElements(
 			CollectionType type,
-			Map<Object, Object> original,
+			Map<?, ?> original,
 			Map<Object, Object> target,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
-		final CollectionPersister persister = session.getFactory().getRuntimeMetamodels()
-				.getMappingMetamodel().getCollectionDescriptor( type.getRole() );
-		final Map<Object, Object> result = target;
-		result.clear();
-
+			SharedSessionContractImplementor session) {
+		final CollectionPersister persister =
+				session.getFactory().getRuntimeMetamodels().getMappingMetamodel()
+						.getCollectionDescriptor( type.getRole() );
+        target.clear();
 		return loop(
 				original.entrySet(), entry -> {
-					final Map.Entry<Object, Object> me = entry;
+					final Map.Entry<?, ?> me = entry;
 					return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache )
 							.thenCompose( key -> getReplace(
 												  persister.getElementType(),
@@ -301,10 +307,10 @@ private static CompletionStage<Object> replaceMapTypeElements(
 												  owner,
 												  session,
 												  copyCache
-										  ).thenAccept( value -> result.put( key, value ) )
+										  ).thenAccept( value -> target.put( key, value ) )
 							);
 				}
-		).thenApply( unused -> result );
+		).thenApply( unused -> target);
 	}
 
 	private static CompletionStage<Object> replaceArrayTypeElements(
@@ -313,7 +319,7 @@ private static CompletionStage<Object> replaceArrayTypeElements(
 			Object target,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		final Object result;
 		final int length = Array.getLength( original );
 		if ( length != Array.getLength( target ) ) {
@@ -325,8 +331,8 @@ private static CompletionStage<Object> replaceArrayTypeElements(
 		}
 
 		final Type elemType = type.getElementType( session.getFactory() );
-		return loop(
-				0, length, i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
+		return loop( 0, length,
+				i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
 						.thenApply( o -> {
 							Array.set( result, i, o );
 							return result;
@@ -338,25 +344,21 @@ private static CompletionStage<Object> getReplace(
 			Type elemType,
 			Object o,
 			Object owner,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Map<Object, Object> copyCache) {
 		return getReplace( elemType, o, null, owner, session, copyCache );
 	}
 
 	private static CompletionStage<Object> getReplace(
 			Type elemType,
-			Object o,
+			Object object,
 			Object target,
 			Object owner,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Map<Object, Object> copyCache) {
-		if ( elemType instanceof EntityType ) {
-			return EntityTypes.replace( (EntityType) elemType, o, target, session, owner, copyCache );
-		}
-		else {
-			final Object replace = elemType.replace( o, target, session, owner, copyCache );
-			return completedFuture( replace );
-		}
+        return elemType instanceof EntityType entityType
+				? EntityTypes.replace( entityType, object, target, session, owner, copyCache )
+				: completedFuture( elemType.replace( object, target, session, owner, copyCache) );
 	}
 
 	/**
@@ -368,13 +370,15 @@ private static CompletionStage<Void> preserveSnapshot(
 			Type elemType,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
 		if ( ce != null ) {
 			return createSnapshot( original, result, elemType, owner, copyCache, session )
 					.thenAccept( serializable -> ce.resetStoredSnapshot( result, serializable ) );
 		}
-		return voidFuture();
+		else {
+			return voidFuture();
+		}
 	}
 
 	/**
@@ -386,7 +390,7 @@ private static CompletionStage<Serializable> createSnapshot(
 			Type elemType,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		final Serializable originalSnapshot = original.getStoredSnapshot();
 		if ( originalSnapshot instanceof List<?> list ) {
 			return createListSnapshot( list, elemType, owner, copyCache, session );
@@ -411,9 +415,9 @@ private static CompletionStage<Serializable> createArraySnapshot(
 			Type elemType,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
-		return loop(
-				0, array.length, i -> getReplace( elemType, array[i], owner, session, copyCache )
+			SharedSessionContractImplementor session) {
+		return loop( 0, array.length,
+				i -> getReplace( elemType, array[i], owner, session, copyCache )
 						.thenAccept( o -> array[i] = o )
 		).thenApply( unused -> array );
 	}
@@ -421,29 +425,26 @@ private static CompletionStage<Serializable> createArraySnapshot(
 	/**
 	 * @see CollectionType#createMapSnapshot(Map, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
 	 */
-	private static CompletionStage<Serializable> createMapSnapshot(
-			Map<?, ?> map,
+	private static <K,V> CompletionStage<Serializable> createMapSnapshot(
+			Map<K,V> map,
 			PersistentCollection<?> result,
 			Type elemType,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		final Map<?, ?> resultSnapshot = (Map<?, ?>) result.getStoredSnapshot();
-		final Map<Object, Object> targetMap;
-		if ( map instanceof SortedMap<?, ?> sortedMap ) {
-			//noinspection unchecked, rawtypes
-			targetMap = new TreeMap( sortedMap.comparator() );
-		}
-		else {
-			targetMap = mapOfSize( map.size() );
-		}
-		return loop(
-				map.entrySet(), entry ->
-						getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
-								.thenAccept( newValue -> {
-									final Object key = entry.getKey();
-									targetMap.put( key == entry.getValue() ? newValue : key, newValue );
-								} )
+		final Map<K, V> targetMap =
+				map instanceof SortedMap<K, V> sortedMap
+						? new TreeMap<>( sortedMap.comparator() )
+						: mapOfSize(map.size());
+        return loop( map.entrySet(),
+				entry -> getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
+						.thenAccept( newValue -> {
+							final K key = entry.getKey();
+							final V value = entry.getValue();
+							//noinspection unchecked
+							targetMap.put( key == value ? (K) newValue : key, (V) newValue );
+						} )
 		).thenApply( v -> (Serializable) targetMap );
 	}
 
@@ -455,10 +456,10 @@ private static CompletionStage<Serializable> createListSnapshot(
 			Type elemType,
 			Object owner,
 			Map<Object, Object> copyCache,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		final ArrayList<Object> targetList = new ArrayList<>( list.size() );
-		return loop(
-				list, obj -> getReplace( elemType, obj, owner, session, copyCache )
+		return loop( list,
+				obj -> getReplace( elemType, obj, owner, session, copyCache )
 						.thenAccept( targetList::add )
 		).thenApply( unused -> targetList );
 	}
@@ -472,9 +473,9 @@ private static Object instantiateResultIfNecessary(CollectionType type, Object o
 		// by default just use an unanticipated capacity since we don't
 		// know how to extract the capacity to use from original here...
 		return target == null
-				|| target == original
-				|| target == UNFETCHED_PROPERTY
-				|| target instanceof PersistentCollection<?> collection && collection.isWrapper( original )
+			|| target == original
+			|| target == UNFETCHED_PROPERTY
+			|| target instanceof PersistentCollection<?> collection && collection.isWrapper( original )
 				? type.instantiate( -1 )
 				: target;
 	}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index 0bb0c75a..50483015 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -16,15 +16,15 @@
 import org.hibernate.engine.spi.PersistenceContext;
 import org.hibernate.engine.spi.PersistentAttributeInterceptor;
 import org.hibernate.engine.spi.SessionFactoryImplementor;
-import org.hibernate.engine.spi.SessionImplementor;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.metamodel.mapping.AttributeMapping;
 import org.hibernate.persister.entity.EntityPersister;
 import org.hibernate.proxy.HibernateProxy;
 import org.hibernate.proxy.LazyInitializer;
+import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor;
 import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
+import org.hibernate.reactive.session.ReactiveQueryProducer;
 import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup;
-import org.hibernate.reactive.session.impl.ReactiveSessionImpl;
 import org.hibernate.type.CollectionType;
 import org.hibernate.type.EntityType;
 import org.hibernate.type.ForeignKeyDirection;
@@ -154,12 +154,12 @@ public static CompletionStage<Object[]> replace(
 			final Object[] original,
 			final Object[] target,
 			final Type[] types,
-			final SessionImplementor session,
+			final SharedSessionContractImplementor session,
 			final Object owner,
 			final Map<Object, Object> copyCache) {
 		Object[] copied = new Object[original.length];
-		return loop(
-				0, types.length, i -> replace( original, target, types, session, owner, copyCache, i, copied )
+		return loop( 0, types.length,
+				i -> replace( original, target, types, session, owner, copyCache, i, copied )
 		).thenApply( v -> copied );
 	}
 
@@ -170,13 +170,12 @@ public static CompletionStage<Object[]> replace(
 			final Object[] original,
 			final Object[] target,
 			final Type[] types,
-			final SessionImplementor session,
+			final SharedSessionContractImplementor session,
 			final Object owner,
 			final Map<Object, Object> copyCache,
 			final ForeignKeyDirection foreignKeyDirection) {
 		Object[] copied = new Object[original.length];
-		return loop(
-				0, types.length,
+		return loop( 0, types.length,
 				i -> replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
 		).thenApply( v -> copied );
 	}
@@ -188,7 +187,7 @@ private static CompletionStage<Object> replace(
 			EntityType entityType,
 			Object original,
 			Object target,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache,
 			ForeignKeyDirection foreignKeyDirection)
@@ -208,7 +207,7 @@ protected static CompletionStage<Object> replace(
 			EntityType entityType,
 			Object original,
 			Object target,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache) {
 		if ( original == null ) {
@@ -254,7 +253,7 @@ protected static CompletionStage<Object> replace(
 	private static CompletionStage<Object> resolveIdOrUniqueKey(
 			EntityType entityType,
 			Object original,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache) {
 		return getIdentifier( entityType, original, session )
@@ -268,7 +267,7 @@ private static CompletionStage<Object> resolveIdOrUniqueKey(
 					// as a ComponentType. In the case that the entity is unfetched, we need to
 					// explicitly fetch it here before calling replace(). (Note that in Hibernate
 					// ORM this is unnecessary due to transparent lazy fetching.)
-					return ( (ReactiveSessionImpl) session )
+					return ( (ReactiveQueryProducer) session )
 							.reactiveFetch( id, true )
 							.thenCompose( fetched -> {
 								Object idOrUniqueKey = entityType
@@ -289,7 +288,7 @@ private static CompletionStage<Object> resolveIdOrUniqueKey(
 	private static CompletionStage<Object> getIdentifier(
 			EntityType entityType,
 			Object value,
-			SessionImplementor session) {
+			SharedSessionContractImplementor session) {
 		if ( entityType.isReferenceToIdentifierProperty() ) {
 			// tolerates nulls
 			return getEntityIdentifierIfNotUnsaved( entityType.getAssociatedEntityName(), value, session );
@@ -340,10 +339,11 @@ private static CompletionStage<Object> getIdentifierFromHibernateProxy(
 			EntityType entityType,
 			HibernateProxy proxy,
 			SharedSessionContractImplementor session) {
-		LazyInitializer initializer = proxy.getHibernateLazyInitializer();
+		final LazyInitializer initializer = proxy.getHibernateLazyInitializer();
 		final String entityName = initializer.getEntityName();
 		final Object identifier = initializer.getIdentifier();
-		return ( (ReactiveSessionImpl) session ).reactiveImmediateLoad( entityName, identifier )
+		return ( (ReactiveSharedSessionContractImplementor) session )
+				.reactiveImmediateLoad( entityName, identifier )
 				.thenApply( entity -> {
 					checkEntityFound( session, entityName, identifier, entity );
 					initializer.setSession( session );
@@ -357,7 +357,7 @@ private static CompletionStage<Object> getIdentifierFromHibernateProxy(
 						// an entity type, in which case we need to resolve its identifier
 						final AttributeMapping type = entityPersister.findAttributeMapping( uniqueKeyPropertyName );
 						if ( type.isEntityIdentifierMapping() ) {
-							propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session );
+							propertyValue = getIdentifier( (EntityType) type, propertyValue, session );
 						}
 						return propertyValue;
 					}
@@ -372,7 +372,8 @@ private static CompletionStage<Object> loadHibernateProxyEntity(
 			LazyInitializer initializer = ( (HibernateProxy) entity ).getHibernateLazyInitializer();
 			final String entityName = initializer.getEntityName();
 			final Object identifier = initializer.getIdentifier();
-			return ( (ReactiveSessionImpl) session ).reactiveImmediateLoad( entityName, identifier )
+			return ( (ReactiveSharedSessionContractImplementor) session )
+					.reactiveImmediateLoad( entityName, identifier )
 					.thenApply( result -> {
 						checkEntityFound( session, entityName, identifier, result );
 						return result;
@@ -387,7 +388,7 @@ private static CompletionStage<Void> replace(
 			Object[] original,
 			Object[] target,
 			Type[] types,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache,
 			int i,
@@ -433,7 +434,7 @@ private static CompletionStage<Void> replace(
 			Object[] original,
 			Object[] target,
 			Type[] types,
-			SessionImplementor session,
+			SharedSessionContractImplementor session,
 			Object owner,
 			Map<Object, Object> copyCache,
 			ForeignKeyDirection foreignKeyDirection,
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java
index 087623e4..87078f87 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java
@@ -15,7 +15,6 @@
 import org.hibernate.engine.spi.EntityEntry;
 import org.hibernate.engine.spi.PersistenceContext;
 import org.hibernate.engine.spi.SelfDirtinessTracker;
-import org.hibernate.engine.spi.SessionImplementor;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.internal.util.StringHelper;
 import org.hibernate.persister.entity.EntityPersister;
@@ -48,7 +47,7 @@ public final class ForeignKeys {
 	public static class Nullifier {
 		private final boolean isDelete;
 		private final boolean isEarlyInsert;
-		private final SessionImplementor session;
+		private final SharedSessionContractImplementor session;
 		private final Object self;
 		private final EntityPersister persister;
 
@@ -65,7 +64,7 @@ public Nullifier(
 				final Object self,
 				final boolean isDelete,
 				final boolean isEarlyInsert,
-				final SessionImplementor session,
+				final SharedSessionContractImplementor session,
 				final EntityPersister persister) {
 			this.isDelete = isDelete;
 			this.isEarlyInsert = isEarlyInsert;
@@ -270,7 +269,7 @@ private CompletionStage<Boolean> isNullifiable(final String entityName, Object o
 	 *
 	 * @return {@code true} if the given entity is not transient (meaning it is either detached/persistent)
 	 */
-	public static CompletionStage<Boolean> isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
+	public static CompletionStage<Boolean> isNotTransient(String entityName, Object entity, Boolean assumed, SharedSessionContractImplementor session) {
 		if ( isHibernateProxy( entity ) ) {
 			return trueFuture();
 		}
@@ -297,7 +296,7 @@ public static CompletionStage<Boolean> isNotTransient(String entityName, Object
 	 *
 	 * @return {@code true} if the given entity is transient (unsaved)
 	 */
-	public static CompletionStage<Boolean> isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
+	public static CompletionStage<Boolean> isTransient(String entityName, Object entity, Boolean assumed, SharedSessionContractImplementor session) {
 		if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
 			// an unfetched association can only point to
 			// an entity that already exists in the db
@@ -350,7 +349,7 @@ public static CompletionStage<Boolean> isTransient(String entityName, Object ent
 	public static CompletionStage<Object> getEntityIdentifierIfNotUnsaved(
 			final String entityName,
 			final Object object,
-			final SessionImplementor session) throws TransientObjectException {
+			final SharedSessionContractImplementor session) throws TransientObjectException {
 		if ( object == null ) {
 			return nullFuture();
 		}
@@ -387,7 +386,7 @@ public static CompletionStage<NonNullableTransientDependencies> findNonNullableT
 
 		final EntityPersister persister = session.getEntityPersister( entityName, entity );
 		final Type[] types = persister.getPropertyTypes();
-		final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, (SessionImplementor) session, persister );
+		final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister );
 		final String[] propertyNames = persister.getPropertyNames();
 		final boolean[] nullability = persister.getPropertyNullability();
 		final NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java
index 4838e697..3f40cdb8 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java
@@ -16,7 +16,6 @@
 import org.hibernate.engine.spi.EntityHolder;
 import org.hibernate.engine.spi.EntityKey;
 import org.hibernate.engine.spi.PersistenceContext;
-import org.hibernate.engine.spi.SessionImplementor;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.engine.spi.Status;
 import org.hibernate.persister.entity.EntityPersister;
@@ -64,7 +63,7 @@ public interface ReactiveEntityInsertAction extends ReactiveExecutable, Comparab
 	// @see org.hibernate.action.internal.AbstractEntityInsertAction#nullifyTransientReferencesIfNotAlready()
 	default CompletionStage<Void> reactiveNullifyTransientReferencesIfNotAlready() {
 		if ( !areTransientReferencesNullified() ) {
-			return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), (SessionImplementor) getSession(), getPersister() )
+			return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() )
 					.nullifyTransientReferences( getState() )
 					.thenAccept( v -> {
 						new Nullability( getSession() ).checkNullability( getState(), getPersister(), false );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java
index 1ab34038..9bdc5742 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java
@@ -9,6 +9,7 @@
 import java.util.concurrent.CompletionStage;
 
 
+import org.hibernate.collection.spi.PersistentCollection;
 import org.hibernate.engine.spi.PersistenceContext;
 
 import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture;
@@ -22,5 +23,9 @@ default CompletionStage<Boolean> reactiveAutoFlushIfRequired(Set<String> querySp
 		return falseFuture();
 	}
 
+	CompletionStage<Object> reactiveImmediateLoad(String entityName, Object id);
+
+	CompletionStage<Void> reactiveInitializeCollection(PersistentCollection<?> collection, boolean writing);
+
 	PersistenceContext getPersistenceContext();
 }
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java
index 20466abd..5547fcc7 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java
@@ -23,9 +23,9 @@
 import org.hibernate.reactive.logging.impl.Log;
 import org.hibernate.reactive.logging.impl.LoggerFactory;
 import org.hibernate.reactive.persister.collection.impl.ReactiveCollectionPersister;
-import org.hibernate.sql.results.internal.ResultsHelper;
 import org.hibernate.stat.spi.StatisticsImplementor;
 
+import static org.hibernate.event.internal.DefaultInitializeCollectionEventListener.handlePotentiallyEmptyCollection;
 import static org.hibernate.pretty.MessageHelper.collectionInfoString;
 import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture;
 import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
@@ -59,7 +59,8 @@ public CompletionStage<Void> onReactiveInitializeCollection(InitializeCollection
 		final CollectionPersister loadedPersister = ce.getLoadedPersister();
 		final Object loadedKey = ce.getLoadedKey();
 		if ( LOG.isTraceEnabled() ) {
-			LOG.tracev( "Initializing collection {0}", collectionInfoString( loadedPersister, collection, loadedKey, source ) );
+			LOG.tracev( "Initializing collection {0}",
+					collectionInfoString( loadedPersister, collection, loadedKey, source ) );
 			LOG.trace( "Checking second-level cache" );
 		}
 
@@ -76,11 +77,8 @@ public CompletionStage<Void> onReactiveInitializeCollection(InitializeCollection
 			}
 			return ( (ReactiveCollectionPersister) loadedPersister )
 					.reactiveInitialize( loadedKey, source )
-					.thenApply( list -> {
-						handlePotentiallyEmptyCollection( collection, source, ce, loadedPersister );
-						return list;
-					} )
-					.thenAccept( list -> {
+					.thenAccept( v -> {
+						handlePotentiallyEmptyCollection( collection, source.getPersistenceContext(), ce, loadedPersister );
 						if ( LOG.isTraceEnabled() ) {
 							LOG.trace( "Collection initialized" );
 						}
@@ -93,23 +91,6 @@ public CompletionStage<Void> onReactiveInitializeCollection(InitializeCollection
 		}
 	}
 
-	private void handlePotentiallyEmptyCollection(
-			PersistentCollection<?> collection,
-			SessionImplementor source,
-			CollectionEntry ce,
-			CollectionPersister loadedPersister) {
-		if ( !collection.wasInitialized() ) {
-			collection.initializeEmptyCollection( loadedPersister );
-			ResultsHelper.finalizeCollectionLoading(
-					source.getPersistenceContext(),
-					loadedPersister,
-					collection,
-					ce.getLoadedKey(),
-					true
-			);
-		}
-	}
-
 	/**
 	 * Try to initialize a collection from the cache
 	 *
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
index c3608061..71048e5b 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
@@ -1745,18 +1745,6 @@ default <T> Uni<T> get(Class<T> entityClass, Object id, LockModeType lockModeTyp
 		@Incubating
 		Uni<Void> upsert(Object entity);
 
-		/**
-		 * Use a SQL {@code merge into} statement to perform an upsert.
-		 *
-		 * @param entityName The entityName for the entity to be merged
-		 * @param entity a detached entity instance
-		 * @throws org.hibernate.TransientObjectException is the entity is transient
-		 *
-		 * @see org.hibernate.StatelessSession#upsert(String, Object)
-		 */
-		@Incubating
-		Uni<Void> upsert(String entityName, Object entity);
-
 		/**
 		 * Use a SQL {@code merge into} statement to perform
 		 * an upsert on multiple rows using the size of the given array
@@ -2281,6 +2269,31 @@ default <T> Uni<T> withStatelessTransaction(Function<StatelessSession, Uni<T>> w
 		 */
 		Statistics getStatistics();
 
+		/**
+		 * Return the current instance of {@link Session}, if any.
+		 * A current session exists only when this method is called
+		 * from within an invocation of {@link #withSession(Function)}
+		 * or {@link #withTransaction(Function)}.
+		 *
+		 * @return the current instance, if any, or {@code null}
+		 *
+		 * @since 3.0
+		 */
+		Session getCurrentSession();
+
+		/**
+		 * Return the current instance of {@link Session}, if any.
+		 * A current session exists only when this method is called
+		 * from within an invocation of
+		 * {@link #withStatelessSession(Function)} or
+		 * {@link #withStatelessTransaction(Function)}.
+		 *
+		 * @return the current instance, if any, or {@code null}
+		 *
+		 * @since 3.0
+		 */
+		StatelessSession getCurrentStatelessSession();
+
 		/**
 		 * Destroy the session factory and clean up its connection pool.
 		 */
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java
new file mode 100644
index 00000000..82f5ccd1
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java
@@ -0,0 +1,364 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.mutiny.delegation;
+
+import io.smallrye.mutiny.Uni;
+import jakarta.persistence.CacheRetrieveMode;
+import jakarta.persistence.CacheStoreMode;
+import jakarta.persistence.EntityGraph;
+import jakarta.persistence.FlushModeType;
+import jakarta.persistence.LockModeType;
+import jakarta.persistence.TypedQueryReference;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaDelete;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.CriteriaUpdate;
+import jakarta.persistence.metamodel.Attribute;
+import org.hibernate.*;
+import org.hibernate.query.criteria.JpaCriteriaInsert;
+import org.hibernate.reactive.common.AffectedEntities;
+import org.hibernate.reactive.common.Identifier;
+import org.hibernate.reactive.common.ResultSetMapping;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Wraps a {@linkplain #delegate} session.
+ *
+ * @author Gavin King
+ */
+public abstract class MutinySessionDelegator implements Mutiny.Session {
+
+    public abstract Mutiny.Session delegate();
+
+    public <T> Uni<T> find(Class<T> entityClass, Object id) {
+        return delegate().find(entityClass, id);
+    }
+
+    public <T> Uni<T> find(Class<T> entityClass, Object id, LockModeType lockModeType) {
+        return delegate().find(entityClass, id, lockModeType);
+    }
+
+    public <T> Uni<T> find(Class<T> entityClass, Object id, LockMode lockMode) {
+        return delegate().find(entityClass, id, lockMode);
+    }
+
+    public <T> Uni<T> find(EntityGraph<T> entityGraph, Object id) {
+        return delegate().find(entityGraph, id);
+    }
+
+    public <T> Uni<List<T>> find(Class<T> entityClass, Object... ids) {
+        return delegate().find(entityClass, ids);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNamedQuery(String queryName, Class<R> resultType) {
+        return delegate().createNamedQuery(queryName, resultType);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createQuery(String queryString, Class<R> resultType) {
+        return delegate().createQuery(queryString, resultType);
+    }
+
+    public boolean isReadOnly(Object entityOrProxy) {
+        return delegate().isReadOnly(entityOrProxy);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, Class<R> resultType, AffectedEntities affectedEntities) {
+        return delegate().createNativeQuery(queryString, resultType, affectedEntities);
+    }
+
+    public boolean isDefaultReadOnly() {
+        return delegate().isDefaultReadOnly();
+    }
+
+    public <T> Uni<T> unproxy(T association) {
+        return delegate().unproxy(association);
+    }
+
+    public Mutiny.MutationQuery createMutationQuery(String queryString) {
+        return delegate().createMutationQuery(queryString);
+    }
+
+    public Uni<Void> close() {
+        return delegate().close();
+    }
+
+    public Mutiny.Session disableFetchProfile(String name) {
+        return delegate().disableFetchProfile(name);
+    }
+
+    public <T> EntityGraph<T> getEntityGraph(Class<T> rootType, String graphName) {
+        return delegate().getEntityGraph(rootType, graphName);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createSelectionQuery(String queryString, Class<R> resultType) {
+        return delegate().createSelectionQuery(queryString, resultType);
+    }
+
+    public Uni<Void> refresh(Object entity, LockModeType lockModeType) {
+        return delegate().refresh(entity, lockModeType);
+    }
+
+    public Uni<Void> lock(Object entity, LockModeType lockModeType) {
+        return delegate().lock(entity, lockModeType);
+    }
+
+    public <R> Mutiny.Query<R> createQuery(TypedQueryReference<R> typedQueryReference) {
+        return delegate().createQuery(typedQueryReference);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, ResultSetMapping<R> resultSetMapping, AffectedEntities affectedEntities) {
+        return delegate().createNativeQuery(queryString, resultSetMapping, affectedEntities);
+    }
+
+    public Uni<Void> lock(Object entity, LockMode lockMode) {
+        return delegate().lock(entity, lockMode);
+    }
+
+    @Incubating
+    public <T> Uni<T> find(Class<T> entityClass, Identifier<T> naturalId) {
+        return delegate().find(entityClass, naturalId);
+    }
+
+    public <T> Uni<T> withTransaction(Function<Mutiny.Transaction, Uni<T>> work) {
+        return delegate().withTransaction(work);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, ResultSetMapping<R> resultSetMapping) {
+        return delegate().createNativeQuery(queryString, resultSetMapping);
+    }
+
+    public <T> EntityGraph<T> createEntityGraph(Class<T> rootType, String graphName) {
+        return delegate().createEntityGraph(rootType, graphName);
+    }
+
+    public Mutiny.Transaction currentTransaction() {
+        return delegate().currentTransaction();
+    }
+
+    public Mutiny.Session detach(Object entity) {
+        return delegate().detach(entity);
+    }
+
+    public Mutiny.Session setCacheStoreMode(CacheStoreMode cacheStoreMode) {
+        return delegate().setCacheStoreMode(cacheStoreMode);
+    }
+
+    public FlushMode getFlushMode() {
+        return delegate().getFlushMode();
+    }
+
+    public LockMode getLockMode(Object entity) {
+        return delegate().getLockMode(entity);
+    }
+
+    public <R> Mutiny.Query<R> createNamedQuery(String queryName) {
+        return delegate().createNamedQuery(queryName);
+    }
+
+    public CriteriaBuilder getCriteriaBuilder() {
+        return delegate().getCriteriaBuilder();
+    }
+
+    public Mutiny.SessionFactory getFactory() {
+        return delegate().getFactory();
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, Class<R> resultType) {
+        return delegate().createNativeQuery(queryString, resultType);
+    }
+
+    public Mutiny.Session setSubselectFetchingEnabled(boolean enabled) {
+        return delegate().setSubselectFetchingEnabled(enabled);
+    }
+
+    public Mutiny.Session setFlushMode(FlushMode flushMode) {
+        return delegate().setFlushMode(flushMode);
+    }
+
+    public Uni<Void> remove(Object entity) {
+        return delegate().remove(entity);
+    }
+
+    public Mutiny.Session setCacheMode(CacheMode cacheMode) {
+        return delegate().setCacheMode(cacheMode);
+    }
+
+    public Filter enableFilter(String filterName) {
+        return delegate().enableFilter(filterName);
+    }
+
+    public Mutiny.MutationQuery createMutationQuery(JpaCriteriaInsert<?> insert) {
+        return delegate().createMutationQuery(insert);
+    }
+
+    public <R> Mutiny.Query<R> createNativeQuery(String queryString, AffectedEntities affectedEntities) {
+        return delegate().createNativeQuery(queryString, affectedEntities);
+    }
+
+    public Mutiny.Session setReadOnly(Object entityOrProxy, boolean readOnly) {
+        return delegate().setReadOnly(entityOrProxy, readOnly);
+    }
+
+    public <T> EntityGraph<T> createEntityGraph(Class<T> rootType) {
+        return delegate().createEntityGraph(rootType);
+    }
+
+    public Uni<Void> refreshAll(Object... entities) {
+        return delegate().refreshAll(entities);
+    }
+
+    public Mutiny.MutationQuery createMutationQuery(CriteriaDelete<?> deleteQuery) {
+        return delegate().createMutationQuery(deleteQuery);
+    }
+
+    public Integer getBatchSize() {
+        return delegate().getBatchSize();
+    }
+
+    public Uni<Void> refresh(Object entity, LockMode lockMode) {
+        return delegate().refresh(entity, lockMode);
+    }
+
+    public <T> T getReference(Class<T> entityClass, Object id) {
+        return delegate().getReference(entityClass, id);
+    }
+
+    public <T> T getReference(T entity) {
+        return delegate().getReference(entity);
+    }
+
+    public Mutiny.Session setBatchSize(Integer batchSize) {
+        return delegate().setBatchSize(batchSize);
+    }
+
+    public Uni<Void> refresh(Object entity) {
+        return delegate().refresh(entity);
+    }
+
+    public CacheMode getCacheMode() {
+        return delegate().getCacheMode();
+    }
+
+    public Uni<Void> mergeAll(Object... entities) {
+        return delegate().mergeAll(entities);
+    }
+
+    public Uni<Void> persist(Object object) {
+        return delegate().persist(object);
+    }
+
+    public boolean contains(Object entity) {
+        return delegate().contains(entity);
+    }
+
+    public Mutiny.MutationQuery createMutationQuery(CriteriaUpdate<?> updateQuery) {
+        return delegate().createMutationQuery(updateQuery);
+    }
+
+    public int getFetchBatchSize() {
+        return delegate().getFetchBatchSize();
+    }
+
+    public Mutiny.Session setDefaultReadOnly(boolean readOnly) {
+        return delegate().setDefaultReadOnly(readOnly);
+    }
+
+    public Mutiny.Session clear() {
+        return delegate().clear();
+    }
+
+    public <E, T> Uni<T> fetch(E entity, Attribute<E, T> field) {
+        return delegate().fetch(entity, field);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createQuery(CriteriaQuery<R> criteriaQuery) {
+        return delegate().createQuery(criteriaQuery);
+    }
+
+    public Mutiny.Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) {
+        return delegate().setCacheRetrieveMode(cacheRetrieveMode);
+    }
+
+    public Uni<Void> removeAll(Object... entities) {
+        return delegate().removeAll(entities);
+    }
+
+    public Filter getEnabledFilter(String filterName) {
+        return delegate().getEnabledFilter(filterName);
+    }
+
+    public void disableFilter(String filterName) {
+        delegate().disableFilter(filterName);
+    }
+
+    public <R> Mutiny.MutationQuery createQuery(CriteriaDelete<R> criteriaDelete) {
+        return delegate().createQuery(criteriaDelete);
+    }
+
+    public Mutiny.Session enableFetchProfile(String name) {
+        return delegate().enableFetchProfile(name);
+    }
+
+    public <T> ResultSetMapping<T> getResultSetMapping(Class<T> resultType, String mappingName) {
+        return delegate().getResultSetMapping(resultType, mappingName);
+    }
+
+    public Uni<Void> flush() {
+        return delegate().flush();
+    }
+
+    public Uni<Void> persist(String entityName, Object object) {
+        return delegate().persist(entityName, object);
+    }
+
+    public <R> Mutiny.Query<R> createNativeQuery(String queryString) {
+        return delegate().createNativeQuery(queryString);
+    }
+
+    public boolean isFetchProfileEnabled(String name) {
+        return delegate().isFetchProfileEnabled(name);
+    }
+
+    public <T> Uni<T> merge(T entity) {
+        return delegate().merge(entity);
+    }
+
+    public boolean isSubselectFetchingEnabled() {
+        return delegate().isSubselectFetchingEnabled();
+    }
+
+    public <R> Mutiny.MutationQuery createQuery(CriteriaUpdate<R> criteriaUpdate) {
+        return delegate().createQuery(criteriaUpdate);
+    }
+
+    public Mutiny.Session setFetchBatchSize(int batchSize) {
+        return delegate().setFetchBatchSize(batchSize);
+    }
+
+    public <T> Uni<T> fetch(T association) {
+        return delegate().fetch(association);
+    }
+
+    public Uni<Void> persistAll(Object... entities) {
+        return delegate().persistAll(entities);
+    }
+
+    @Deprecated
+    public <R> Mutiny.Query<R> createQuery(String queryString) {
+        return delegate().createQuery(queryString);
+    }
+
+    public Mutiny.Session setFlushMode(FlushModeType flushModeType) {
+        return delegate().setFlushMode(flushModeType);
+    }
+
+    public boolean isOpen() {
+        return delegate().isOpen();
+    }
+}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java
new file mode 100644
index 00000000..ca19b71e
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java
@@ -0,0 +1,277 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.mutiny.delegation;
+
+import io.smallrye.mutiny.Uni;
+import jakarta.persistence.EntityGraph;
+import jakarta.persistence.LockModeType;
+import jakarta.persistence.TypedQueryReference;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaDelete;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.CriteriaUpdate;
+import org.hibernate.Incubating;
+import org.hibernate.LockMode;
+import org.hibernate.query.criteria.JpaCriteriaInsert;
+import org.hibernate.reactive.common.AffectedEntities;
+import org.hibernate.reactive.common.ResultSetMapping;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Wraps a {@linkplain #delegate} stateless session.
+ *
+ * @author Gavin King
+ */
+public abstract class MutinyStatelessSessionDelegator implements Mutiny.StatelessSession {
+
+    public abstract Mutiny.StatelessSession delegate();
+
+    public <T> Uni<T> get(Class<T> entityClass, Object id) {
+        return delegate().get(entityClass, id);
+    }
+
+    @Deprecated
+    public <R> Mutiny.Query<R> createQuery(String queryString) {
+        return delegate().createQuery(queryString);
+    }
+
+    public <T> Uni<T> get(EntityGraph<T> entityGraph, Object id) {
+        return delegate().get(entityGraph, id);
+    }
+
+    public <T> Uni<List<T>> get(Class<T> entityClass, Object... ids) {
+        return delegate().get(entityClass, ids);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, ResultSetMapping<R> resultSetMapping) {
+        return delegate().createNativeQuery(queryString, resultSetMapping);
+    }
+
+    public Object getIdentifier(Object entity) {
+        return delegate().getIdentifier(entity);
+    }
+
+    public <T> Uni<T> get(Class<T> entityClass, Object id, LockMode lockMode) {
+        return delegate().get(entityClass, id, lockMode);
+    }
+
+    public boolean isOpen() {
+        return delegate().isOpen();
+    }
+
+    public CriteriaBuilder getCriteriaBuilder() {
+        return delegate().getCriteriaBuilder();
+    }
+
+    public Uni<Void> insertAll(Object... entities) {
+        return delegate().insertAll(entities);
+    }
+
+    public Uni<Void> updateAll(int batchSize, Object... entities) {
+        return delegate().updateAll(batchSize, entities);
+    }
+
+    public <T> EntityGraph<T> createEntityGraph(Class<T> rootType, String graphName) {
+        return delegate().createEntityGraph(rootType, graphName);
+    }
+
+    public <T> EntityGraph<T> getEntityGraph(Class<T> rootType, String graphName) {
+        return delegate().getEntityGraph(rootType, graphName);
+    }
+
+    public <T> Uni<T> get(Class<T> entityClass, Object id, LockModeType lockModeType) {
+        return delegate().get(entityClass, id, lockModeType);
+    }
+
+    public Uni<Void> update(Object entity) {
+        return delegate().update(entity);
+    }
+
+    public Uni<Void> refreshAll(int batchSize, Object... entities) {
+        return delegate().refreshAll(batchSize, entities);
+    }
+
+    public <R> Mutiny.Query<R> createQuery(TypedQueryReference<R> typedQueryReference) {
+        return delegate().createQuery(typedQueryReference);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createQuery(String queryString, Class<R> resultType) {
+        return delegate().createQuery(queryString, resultType);
+    }
+
+    public Uni<Void> delete(Object entity) {
+        return delegate().delete(entity);
+    }
+
+    public Uni<Void> refresh(Object entity, LockModeType lockModeType) {
+        return delegate().refresh(entity, lockModeType);
+    }
+
+    public Mutiny.SessionFactory getFactory() {
+        return delegate().getFactory();
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, Class<R> resultType) {
+        return delegate().createNativeQuery(queryString, resultType);
+    }
+
+    public Uni<Void> deleteMultiple(List<?> entities) {
+        return delegate().deleteMultiple(entities);
+    }
+
+    public Uni<Void> deleteAll(Object... entities) {
+        return delegate().deleteAll(entities);
+    }
+
+    public Uni<Void> updateMultiple(List<?> entities) {
+        return delegate().updateMultiple(entities);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createSelectionQuery(String queryString, Class<R> resultType) {
+        return delegate().createSelectionQuery(queryString, resultType);
+    }
+
+    @Incubating
+    public Uni<Void> upsert(Object entity) {
+        return delegate().upsert(entity);
+    }
+
+    public Mutiny.MutationQuery createMutationQuery(String queryString) {
+        return delegate().createMutationQuery(queryString);
+    }
+
+    public Uni<Void> refresh(Object entity) {
+        return delegate().refresh(entity);
+    }
+
+    public <T> ResultSetMapping<T> getResultSetMapping(Class<T> resultType, String mappingName) {
+        return delegate().getResultSetMapping(resultType, mappingName);
+    }
+
+    @Incubating
+    public Uni<Void> upsertMultiple(List<?> entities) {
+        return delegate().upsertMultiple(entities);
+    }
+
+    public <R> Mutiny.MutationQuery createQuery(CriteriaUpdate<R> criteriaUpdate) {
+        return delegate().createQuery(criteriaUpdate);
+    }
+
+    public Uni<Void> insertAll(int batchSize, Object... entities) {
+        return delegate().insertAll(batchSize, entities);
+    }
+
+    public <R> Mutiny.Query<R> createNativeQuery(String queryString) {
+        return delegate().createNativeQuery(queryString);
+    }
+
+    public <R> Mutiny.Query<R> createNamedQuery(String queryName) {
+        return delegate().createNamedQuery(queryName);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createNamedQuery(String queryName, Class<R> resultType) {
+        return delegate().createNamedQuery(queryName, resultType);
+    }
+
+    public Uni<Void> refreshAll(Object... entities) {
+        return delegate().refreshAll(entities);
+    }
+
+    public Uni<Void> close() {
+        return delegate().close();
+    }
+
+    public Uni<Void> updateAll(Object... entities) {
+        return delegate().updateAll(entities);
+    }
+
+    public <R> Mutiny.SelectionQuery<R> createQuery(CriteriaQuery<R> criteriaQuery) {
+        return delegate().createQuery(criteriaQuery);
+    }
+
+    public <R> Mutiny.MutationQuery createQuery(CriteriaDelete<R> criteriaDelete) {
+        return delegate().createQuery(criteriaDelete);
+    }
+
+    public <T> Uni<T> withTransaction(Function<Mutiny.Transaction, Uni<T>> work) {
+        return delegate().withTransaction(work);
+    }
+
+    public Uni<Void> refreshMultiple(List<?> entities) {
+        return delegate().refreshMultiple(entities);
+    }
+
+    public <T> Uni<T> fetch(T association) {
+        return delegate().fetch(association);
+    }
+
+    @Incubating
+    public Uni<Void> upsertAll(Object... entities) {
+        return delegate().upsertAll(entities);
+    }
+
+    public Mutiny.Transaction currentTransaction() {
+        return delegate().currentTransaction();
+    }
+
+    @Incubating
+    public Uni<Void> upsertAll(int batchSize, Object... entities) {
+        return delegate().upsertAll(batchSize, entities);
+    }
+
+    public <T> EntityGraph<T> createEntityGraph(Class<T> rootType) {
+        return delegate().createEntityGraph(rootType);
+    }
+
+    public Uni<Void> insert(Object entity) {
+        return delegate().insert(entity);
+    }
+
+    public Uni<Void> insertMultiple(List<?> entities) {
+        return delegate().insertMultiple(entities);
+    }
+
+    public Uni<Void> refresh(Object entity, LockMode lockMode) {
+        return delegate().refresh(entity, lockMode);
+    }
+
+    public Uni<Void> deleteAll(int batchSize, Object... entities) {
+        return delegate().deleteAll(batchSize, entities);
+    }
+
+    @Override
+    public Mutiny.MutationQuery createMutationQuery(CriteriaUpdate<?> updateQuery) {
+        return delegate().createMutationQuery(updateQuery);
+    }
+
+    @Override
+    public Mutiny.MutationQuery createMutationQuery(CriteriaDelete<?> deleteQuery) {
+        return delegate().createMutationQuery(deleteQuery);
+    }
+
+    @Override
+    public Mutiny.MutationQuery createMutationQuery(JpaCriteriaInsert<?> insert) {
+        return delegate().createMutationQuery(insert);
+    }
+
+    @Override
+    public <R> Mutiny.Query<R> createNativeQuery(String queryString, AffectedEntities affectedEntities) {
+        return delegate().createNativeQuery(queryString, affectedEntities);
+    }
+
+    @Override
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, Class<R> resultType, AffectedEntities affectedEntities) {
+        return delegate().createNativeQuery(queryString, resultType, affectedEntities);
+    }
+
+    @Override
+    public <R> Mutiny.SelectionQuery<R> createNativeQuery(String queryString, ResultSetMapping<R> resultSetMapping, AffectedEntities affectedEntities) {
+        return delegate().createNativeQuery(queryString, resultSetMapping, affectedEntities);
+    }
+}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java
index f21bed53..46a71b0b 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java
@@ -148,6 +148,16 @@ private CompletionStage<ReactiveConnection> connection(String tenantId) {
 				: connectionPool.getConnection( tenantId );
 	}
 
+	@Override
+	public Mutiny.Session getCurrentSession() {
+		return context.get( contextKeyForSession );
+	}
+
+	@Override
+	public Mutiny.StatelessSession getCurrentStatelessSession() {
+		return context.get( contextKeyForStatelessSession );
+	}
+
 	@Override
 	public <T> Uni<T> withSession(Function<Mutiny.Session, Uni<T>> work) {
 		Objects.requireNonNull( work, "parameter 'work' is required" );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java
index 8a414910..c80be96e 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java
@@ -332,36 +332,22 @@ public Uni<Void> lock(Object entity, LockOptions lockOptions) {
 
 	@Override
 	public FlushMode getFlushMode() {
-		switch ( delegate.getHibernateFlushMode() ) {
-			case MANUAL:
-				return FlushMode.MANUAL;
-			case COMMIT:
-				return FlushMode.COMMIT;
-			case AUTO:
-				return FlushMode.AUTO;
-			case ALWAYS:
-				return FlushMode.ALWAYS;
-			default:
-				throw LOG.impossibleFlushModeIllegalState();
-		}
+        return switch (delegate.getHibernateFlushMode()) {
+            case MANUAL -> FlushMode.MANUAL;
+            case COMMIT -> FlushMode.COMMIT;
+            case AUTO -> FlushMode.AUTO;
+            case ALWAYS -> FlushMode.ALWAYS;
+        };
 	}
 
 	@Override
 	public Mutiny.Session setFlushMode(FlushMode flushMode) {
-		switch ( flushMode ) {
-			case COMMIT:
-				delegate.setHibernateFlushMode( FlushMode.COMMIT );
-				break;
-			case AUTO:
-				delegate.setHibernateFlushMode( FlushMode.AUTO );
-				break;
-			case MANUAL:
-				delegate.setHibernateFlushMode( FlushMode.MANUAL );
-				break;
-			case ALWAYS:
-				delegate.setHibernateFlushMode( FlushMode.ALWAYS );
-				break;
-		}
+		delegate.setHibernateFlushMode( switch ( flushMode ) {
+			case COMMIT -> org.hibernate.FlushMode.COMMIT;
+			case AUTO -> org.hibernate.FlushMode.AUTO;
+			case MANUAL -> org.hibernate.FlushMode.MANUAL;
+			case ALWAYS -> org.hibernate.FlushMode.ALWAYS;
+		} );
 		return this;
 	}
 
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java
index f5a6352c..21602c48 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java
@@ -240,11 +240,6 @@ public Uni<Void> upsert(Object entity) {
 		return uni( () -> delegate.reactiveUpsert( entity ) );
 	}
 
-	@Override
-	public Uni<Void> upsert(String entityName, Object entity) {
-		return uni( () -> delegate.reactiveUpsert( entityName, entity ) );
-	}
-
 	@Override
 	public Uni<Void> upsertAll(Object... entities) {
 		return uni( () -> delegate.reactiveUpsertAll( entities.length, entities ) );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java
index c1015939..0ebebe60 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java
@@ -55,6 +55,7 @@
 import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
 import org.hibernate.persister.entity.AbstractEntityPersister;
 import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor;
+import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor;
 import org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper;
 import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan;
 import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader;
@@ -62,7 +63,6 @@
 import org.hibernate.reactive.metamodel.mapping.internal.ReactiveCompoundNaturalIdMapping;
 import org.hibernate.reactive.metamodel.mapping.internal.ReactiveSimpleNaturalIdMapping;
 import org.hibernate.reactive.pool.ReactiveConnection;
-import org.hibernate.reactive.session.ReactiveSession;
 import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup;
 import org.hibernate.sql.SimpleSelect;
 import org.hibernate.sql.Update;
@@ -359,7 +359,8 @@ else if ( result instanceof PersistentCollection ) {
 			final PersistentCollection<?> collection = (PersistentCollection<?>) result;
 			return collection.wasInitialized()
 					? completedFuture( (T) collection )
-					: ( (ReactiveSession) session ).reactiveInitializeCollection( collection, false )
+					: ( (ReactiveSharedSessionContractImplementor) session )
+							.reactiveInitializeCollection( collection, false )
 							.thenApply( v -> (T) result );
 		}
 		else {
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java
index 88cfe3ec..ab2089c0 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java
@@ -16,7 +16,6 @@
 import org.hibernate.LockMode;
 import org.hibernate.LockOptions;
 import org.hibernate.UnknownProfileException;
-import org.hibernate.collection.spi.PersistentCollection;
 import org.hibernate.engine.spi.EntityEntry;
 import org.hibernate.engine.spi.SessionImplementor;
 import org.hibernate.event.spi.DeleteContext;
@@ -45,6 +44,7 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe
 
 	ReactiveActionQueue getReactiveActionQueue();
 
+	@Override
 	SessionImplementor getSharedContract();
 
 	<E,T> CompletionStage<T> reactiveFetch(E entity, Attribute<E,T> field);
@@ -59,8 +59,6 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe
 
 	CompletionStage<Void> reactiveRemove(Object entity);
 
-	CompletionStage<Void> reactiveRemove(String entityName, boolean isCascadeDeleteEnabled, DeleteContext transientObjects);
-
 	CompletionStage<Void> reactiveRemove(String entityName, Object child, boolean isCascadeDeleteEnabled, DeleteContext transientEntities);
 
 	<T> CompletionStage<T> reactiveMerge(T object);
@@ -91,10 +89,6 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe
 
 	<T> CompletionStage<T> reactiveFind(Class<T> entityClass, Map<String,Object> naturalIds);
 
-	CompletionStage<Object> reactiveImmediateLoad(String entityName, Object id);
-
-	CompletionStage<Void> reactiveInitializeCollection(PersistentCollection<?> collection, boolean writing);
-
 	CompletionStage<Void> reactiveRemoveOrphanBeforeUpdates(String entityName, Object child);
 
 	void setHibernateFlushMode(FlushMode flushMode);
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java
index e6d2aa1e..006fbb63 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java
@@ -46,18 +46,12 @@ public interface ReactiveStatelessSession extends ReactiveQueryProducer, Reactiv
 
 	CompletionStage<Void> reactiveUpsert(Object entity);
 
-	CompletionStage<Void> reactiveUpsert(String entityName, Object entity);
-
 	CompletionStage<Void> reactiveUpsertAll(int batchSize, Object... entities);
 
 	CompletionStage<Void> reactiveRefresh(Object entity);
 
-	CompletionStage<Void> reactiveRefresh(String entityName, Object entity);
-
 	CompletionStage<Void> reactiveRefresh(Object entity, LockMode lockMode);
 
-	CompletionStage<Void> reactiveRefresh(String entityName, Object entity, LockMode lockMode);
-
 	CompletionStage<Void> reactiveInsertAll(Object... entities);
 
 	CompletionStage<Void> reactiveInsertAll(int batchSize, Object... entities);
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java
index 02b392a2..d86ffd7d 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java
@@ -30,14 +30,12 @@
 import org.hibernate.engine.spi.PersistentAttributeInterceptor;
 import org.hibernate.engine.spi.SessionImplementor;
 import org.hibernate.engine.spi.Status;
-import org.hibernate.event.service.spi.EventListenerGroup;
 import org.hibernate.event.spi.AutoFlushEvent;
 import org.hibernate.event.spi.DeleteContext;
 import org.hibernate.event.spi.DeleteEvent;
 import org.hibernate.event.spi.EventSource;
 import org.hibernate.event.spi.FlushEvent;
 import org.hibernate.event.spi.InitializeCollectionEvent;
-import org.hibernate.event.spi.InitializeCollectionEventListener;
 import org.hibernate.event.spi.LoadEvent;
 import org.hibernate.event.spi.LoadEventListener;
 import org.hibernate.event.spi.LockEvent;
@@ -826,8 +824,7 @@ public CompletionStage<Void> reactiveInitializeCollection(PersistentCollection<?
 		pulseTransactionCoordinator();
 		InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this );
 
-		EventListenerGroup<InitializeCollectionEventListener> eventListenerGroupInitCollection = getFactory().getEventListenerGroups().eventListenerGroup_INIT_COLLECTION;
-		return eventListenerGroupInitCollection
+        return getFactory().getEventListenerGroups().eventListenerGroup_INIT_COLLECTION
 				.fireEventOnEachListener(
 						event,
 						(DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection
@@ -920,16 +917,6 @@ public CompletionStage<Void> reactiveRemove(Object entity) {
 		return fireRemove( new DeleteEvent( entity, this ) );
 	}
 
-	@Override
-	public CompletionStage<Void> reactiveRemove(
-			String entityName,
-			boolean isCascadeDeleteEnabled,
-			DeleteContext transientEntities)
-			throws HibernateException {
-		// I'm not quite sure if we need this method
-		return reactiveRemove( entityName, null, isCascadeDeleteEnabled, transientEntities );
-	}
-
 	@Override
 	public CompletionStage<Void> reactiveRemove(
 			String entityName,
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java
index 84483afb..be9dc87b 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java
@@ -7,6 +7,7 @@
 
 import org.hibernate.HibernateException;
 import org.hibernate.LockMode;
+import org.hibernate.SessionException;
 import org.hibernate.UnknownEntityTypeException;
 import org.hibernate.UnresolvableObjectException;
 import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
@@ -14,6 +15,7 @@
 import org.hibernate.collection.spi.PersistentCollection;
 import org.hibernate.dialect.Dialect;
 import org.hibernate.engine.internal.ReactivePersistenceContextAdapter;
+import org.hibernate.engine.spi.CollectionEntry;
 import org.hibernate.engine.spi.LoadQueryInfluencers;
 import org.hibernate.engine.spi.PersistenceContext;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
@@ -101,6 +103,7 @@
 import static org.hibernate.internal.util.StringHelper.isNotEmpty;
 import static org.hibernate.loader.ast.spi.CascadingFetchProfile.REFRESH;
 import static org.hibernate.loader.internal.CacheLoadHelper.initializeCollectionFromCache;
+import static org.hibernate.pretty.MessageHelper.collectionInfoString;
 import static org.hibernate.pretty.MessageHelper.infoString;
 import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
 import static org.hibernate.reactive.id.impl.IdentifierGeneration.castToIdentifierType;
@@ -590,18 +593,12 @@ public CompletionStage<Void> reactiveRefresh(Object entity) {
 		return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE );
 	}
 
-	@Override
-	public CompletionStage<Void> reactiveRefresh(String entityName, Object entity) {
-		return reactiveRefresh( entityName, entity, LockMode.NONE );
-	}
-
 	@Override
 	public CompletionStage<Void> reactiveRefresh(Object entity, LockMode lockMode) {
 		return reactiveRefresh( bestGuessEntityName( entity ), entity, lockMode );
 	}
 
-	@Override
-	public CompletionStage<Void> reactiveRefresh(String entityName, Object entity, LockMode lockMode) {
+	private CompletionStage<Void> reactiveRefresh(String entityName, Object entity, LockMode lockMode) {
 		checkOpen();
 		final ReactiveEntityPersister persister = getEntityPersister( entityName, entity );
 		final Object id = persister.getIdentifier( entity, this );
@@ -642,16 +639,7 @@ private CompletionStage<?> fromInternalFetchProfile(CascadingFetchProfile cascad
 	@Override
 	public CompletionStage<Void> reactiveUpsert(Object entity) {
 		checkOpen();
-		return reactiveUpsert( null, entity );
-	}
-
-	/**
-	 * @see StatelessSessionImpl#upsert(String, Object)
-	 */
-	@Override
-	public CompletionStage<Void> reactiveUpsert(String entityName, Object entity) {
-		checkOpen();
-		final ReactiveEntityPersister persister = getEntityPersister( entityName, entity );
+		final ReactiveEntityPersister persister = getEntityPersister( null, entity );
 		final Object id = idToUpsert( entity, persister );
 		final Object[] state = persister.getValues( entity );
 		if ( firePreUpsert( entity, id, state, persister ) ) {
@@ -764,6 +752,15 @@ public CompletionStage<Object> reactiveInternalLoad(String entityName, Object id
 				: completedFuture( object );
 	}
 
+	@Override
+	public CompletionStage<Object> reactiveImmediateLoad(String entityName, Object id) {
+		if ( persistenceContext.isLoadFinished() ) {
+			throw new SessionException( "proxies cannot be fetched by a stateless session" );
+		}
+		// unless we are still in the process of handling a top-level load
+		return reactiveGet( entityName, id );
+	}
+
 	@Override
 	protected Object internalLoadGet(String entityName, Object id, PersistenceContext persistenceContext) {
 		// otherwise immediately materialize it
@@ -775,6 +772,44 @@ protected Object internalLoadGet(String entityName, Object id, PersistenceContex
 				.whenComplete( (r, e) -> persistenceContext.afterLoad() );
 	}
 
+	@Override
+	public CompletionStage<Void> reactiveInitializeCollection(PersistentCollection<?> collection, boolean writing) {
+		checkOpen();
+		final CollectionEntry ce = persistenceContext.getCollectionEntry( collection );
+		if ( ce == null ) {
+			throw new HibernateException( "no entry for collection" );
+		}
+        if ( collection.wasInitialized() ) {
+            return voidFuture();
+        }
+		else {
+            final ReactiveCollectionPersister loadedPersister =
+                    (ReactiveCollectionPersister) ce.getLoadedPersister();
+            final Object loadedKey = ce.getLoadedKey();
+            if ( LOG.isTraceEnabled() ) {
+                LOG.trace( "Initializing collection "
+                        + collectionInfoString( loadedPersister, collection, loadedKey, this ) );
+            }
+            final boolean foundInCache =
+                    initializeCollectionFromCache( loadedKey, loadedPersister, collection, this );
+            if ( foundInCache ) {
+                LOG.trace( "Collection initialized from cache" );
+                return voidFuture();
+            }
+            else {
+                return loadedPersister.reactiveInitialize( loadedKey, this )
+                        .thenAccept( v -> {
+                            handlePotentiallyEmptyCollection( collection, persistenceContext, loadedKey, loadedPersister );
+                            LOG.trace( "Collection initialized" );
+                            final StatisticsImplementor statistics = getFactory().getStatistics();
+                            if ( statistics.isStatisticsEnabled() ) {
+                                statistics.fetchCollection( loadedPersister.getRole() );
+                            }
+                        } );
+            }
+        }
+    }
+
 	@Override
 	public <T> CompletionStage<T> reactiveFetch(T association, boolean unproxy) {
 		checkOpen();
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java
index 981c2b70..a97b05e5 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java
@@ -1806,15 +1806,6 @@ default CompletionStage<Void> refresh(Object entity, LockModeType lockModeType)
 		 */
 		CompletionStage<Void> upsert(Object entity);
 
-		/**
-		 *
-		 * @param entityName The entityName for the entity to be merged
-		 * @param entity a detached entity instance
-		 *
-		 * @see org.hibernate.StatelessSession#upsert(String, Object)
-		 */
-		CompletionStage<Void> upsert(String entityName, Object entity);
-
 		/**
 		 * Use a SQL {@code merge into} statement to perform
 		 * an upsert on multiple rows using the size of the given array
@@ -2282,6 +2273,31 @@ default <T> CompletionStage<T> withStatelessTransaction(Function<StatelessSessio
 		 */
 		Statistics getStatistics();
 
+		/**
+		 * Return the current instance of {@link Session}, if any.
+		 * A current session exists only when this method is called
+		 * from within an invocation of {@link #withSession(Function)}
+		 * or {@link #withTransaction(Function)}.
+		 *
+		 * @return the current instance, if any, or {@code null}
+		 *
+		 * @since 3.0
+		 */
+		Session getCurrentSession();
+
+		/**
+		 * Return the current instance of {@link Session}, if any.
+		 * A current session exists only when this method is called
+		 * from within an invocation of
+		 * {@link #withStatelessSession(Function)} or
+		 * {@link #withStatelessTransaction(Function)}.
+		 *
+		 * @return the current instance, if any, or {@code null}
+		 *
+		 * @since 3.0
+		 */
+		StatelessSession getCurrentStatelessSession();
+
 		/**
 		 * Destroy the session factory and clean up its connection pool.
 		 */
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java
index 78d52e68..56c574ce 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java
@@ -138,6 +138,16 @@ private CompletionStage<ReactiveConnection> connection(String tenantId) {
 				: connectionPool.getConnection( tenantId );
 	}
 
+	@Override
+	public Stage.Session getCurrentSession() {
+		return context.get( contextKeyForSession );
+	}
+
+	@Override
+	public Stage.StatelessSession getCurrentStatelessSession() {
+		return context.get( contextKeyForStatelessSession );
+	}
+
 	@Override
 	public <T> CompletionStage<T> withSession(Function<Stage.Session, CompletionStage<T>> work) {
 		Objects.requireNonNull( work, "parameter 'work' is required" );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java
index 32227de7..da09a284 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java
@@ -19,8 +19,6 @@
 import org.hibernate.reactive.common.ResultSetMapping;
 import org.hibernate.reactive.engine.ReactiveActionQueue;
 import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor;
-import org.hibernate.reactive.logging.impl.Log;
-import org.hibernate.reactive.logging.impl.LoggerFactory;
 import org.hibernate.reactive.pool.ReactiveConnection;
 import org.hibernate.reactive.query.ReactiveQuery;
 import org.hibernate.reactive.session.ReactiveConnectionSupplier;
@@ -43,7 +41,6 @@
 import jakarta.persistence.criteria.CriteriaQuery;
 import jakarta.persistence.criteria.CriteriaUpdate;
 import jakarta.persistence.metamodel.Attribute;
-import java.lang.invoke.MethodHandles;
 import java.util.List;
 import java.util.concurrent.CompletionStage;
 import java.util.function.Function;
@@ -59,8 +56,6 @@
  */
 public class StageSessionImpl implements Stage.Session {
 
-	private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
-
 	private final ReactiveSession delegate;
 
 	public StageSessionImpl(ReactiveSession session) {
@@ -252,38 +247,22 @@ public CompletionStage<Void> lock(Object entity, LockOptions lockOptions) {
 
 	@Override
 	public FlushMode getFlushMode() {
-		switch ( delegate.getHibernateFlushMode() ) {
-			case MANUAL:
-				return FlushMode.MANUAL;
-			case COMMIT:
-				return FlushMode.COMMIT;
-			case AUTO:
-				return FlushMode.AUTO;
-			case ALWAYS:
-				return FlushMode.ALWAYS;
-			default:
-				throw LOG.impossibleFlushModeIllegalState();
-		}
+        return switch ( delegate.getHibernateFlushMode() ) {
+            case MANUAL -> FlushMode.MANUAL;
+            case COMMIT -> FlushMode.COMMIT;
+            case AUTO -> FlushMode.AUTO;
+            case ALWAYS -> FlushMode.ALWAYS;
+        };
 	}
 
 	@Override
 	public Stage.Session setFlushMode(FlushMode flushMode) {
-		switch ( flushMode ) {
-			case COMMIT:
-				delegate.setHibernateFlushMode( org.hibernate.FlushMode.COMMIT );
-				break;
-			case AUTO:
-				delegate.setHibernateFlushMode( org.hibernate.FlushMode.AUTO );
-				break;
-			case MANUAL:
-				delegate.setHibernateFlushMode( org.hibernate.FlushMode.MANUAL );
-				break;
-			case ALWAYS:
-				delegate.setHibernateFlushMode( org.hibernate.FlushMode.ALWAYS );
-				break;
-			default:
-				throw new IllegalArgumentException( "Unsupported flushMode: " + flushMode );
-		}
+		delegate.setHibernateFlushMode( switch ( flushMode ) {
+			case COMMIT -> org.hibernate.FlushMode.COMMIT;
+			case AUTO -> org.hibernate.FlushMode.AUTO;
+			case MANUAL -> org.hibernate.FlushMode.MANUAL;
+			case ALWAYS -> org.hibernate.FlushMode.ALWAYS;
+		} );
 		return this;
 	}
 
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java
index 4c325fdf..07f7ef35 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java
@@ -154,11 +154,6 @@ public CompletionStage<Void> upsert(Object entity) {
 		return delegate.reactiveUpsert( entity );
 	}
 
-	@Override
-	public CompletionStage<Void> upsert(String entityName, Object entity) {
-		return delegate.reactiveUpsert( entityName, entity );
-	}
-
 	@Override
 	public CompletionStage<Void> upsertAll(Object... entities) {
 		return delegate.reactiveUpsertAll( entities.length, entities );
diff --git a/jreleaser.yml b/jreleaser.yml
index c3eeec64..f9428278 100644
--- a/jreleaser.yml
+++ b/jreleaser.yml
@@ -27,16 +27,6 @@ deploy:
         releaseRepository: false
         stagingRepositories:
           - build/staging-deploy/maven
-      maven-central-snapshot:
-        active: SNAPSHOT
-        url: https://oss.sonatype.org/service/local
-        snapshotUrl: https://oss.sonatype.org/content/repositories/snapshots/
-        closeRepository: true
-        releaseRepository: true
-        javadocJar: false
-        sign: false
-        stagingRepositories:
-          - build/staging-deploy/maven
     mavenCentral:
       maven-central:
         # TODO: Change to RELEASE once switching to Maven-Central:
@@ -47,13 +37,3 @@ deploy:
         applyMavenCentralRules: true
         stagingRepositories:
           - build/staging-deploy/maven
-      maven-central-snapshot:
-        # TODO: Change to SNAPSHOT once switching to Maven-Central:
-        active: NEVER
-        url: https://central.sonatype.com/api/v1/publisher
-        snapshotSupported: true
-        applyMavenCentralRules: true
-        javadocJar: false
-        sign: false
-        stagingRepositories:
-          - build/staging-deploy/maven
diff --git a/publish.gradle b/publish.gradle
index 201b67a8..f77b2695 100644
--- a/publish.gradle
+++ b/publish.gradle
@@ -79,5 +79,12 @@ publishing {
             name = "staging"
             url = rootProject.layout.buildDirectory.dir("staging-deploy${File.separator}maven")
         }
+        maven {
+            name = 'snapshots'
+            url = "https://oss.sonatype.org/content/repositories/snapshots/"
+            // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername`
+            //  env variables to read the username/password for the `snapshots` repository publishing:
+            credentials(PasswordCredentials)
+        }
     }
 }


Test patch:
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java
index fb8c7e1f..bf46f835 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java
@@ -27,12 +27,7 @@
 import jakarta.persistence.metamodel.EntityType;
 
 import static java.util.concurrent.TimeUnit.MINUTES;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.*;
 
 @Timeout(value = 10, timeUnit = MINUTES)
 
@@ -668,6 +663,38 @@ public void testForceFlushWithDelete(VertxTestContext context) {
 		);
 	}
 
+	@Test
+	public void testCurrentSession(VertxTestContext context) {
+		test( context,
+				getMutinySessionFactory().withSession(session ->
+						getMutinySessionFactory().withSession(s -> {
+							assertEquals(session, s);
+							Mutiny.Session currentSession = getMutinySessionFactory().getCurrentSession();
+							assertNotNull(currentSession);
+							assertTrue(currentSession.isOpen());
+							assertEquals(session, currentSession);
+							return Uni.createFrom().voidItem();
+						}).invoke(() -> assertNotNull(getMutinySessionFactory().getCurrentSession()))
+				).invoke(() -> assertNull(getMutinySessionFactory().getCurrentSession()))
+		);
+	}
+
+	@Test
+	public void testCurrentStatelessSession(VertxTestContext context) {
+		test( context,
+				getMutinySessionFactory().withStatelessSession(session ->
+						getMutinySessionFactory().withStatelessSession(s -> {
+							assertEquals(session, s);
+							Mutiny.StatelessSession currentSession = getMutinySessionFactory().getCurrentStatelessSession();
+							assertNotNull(currentSession);
+							assertTrue(currentSession.isOpen());
+							assertEquals(session, currentSession);
+							return Uni.createFrom().voidItem();
+						}).invoke(() -> assertNotNull(getMutinySessionFactory().getCurrentStatelessSession()))
+				).invoke(() -> assertNull(getMutinySessionFactory().getCurrentStatelessSession()))
+		);
+	}
+
 	private void assertThatPigsAreEqual(GuineaPig expected, GuineaPig actual) {
 		assertNotNull( actual );
 		assertEquals( expected.getId(), actual.getId() );
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java
index 78494bd1..ed0c724f 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java
@@ -32,12 +32,7 @@
 
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
 
 @Timeout(value = 10, timeUnit = MINUTES)
 
@@ -969,6 +964,42 @@ context, openSession()
 		);
 	}
 
+	@Test
+	public void testCurrentSession(VertxTestContext context) {
+		test( context,
+				getSessionFactory().withSession(session ->
+						getSessionFactory().withSession(s -> {
+							assertEquals(session, s);
+							Stage.Session currentSession = getSessionFactory().getCurrentSession();
+							assertNotNull(currentSession);
+							assertTrue(currentSession.isOpen());
+							assertEquals(session, currentSession);
+							return CompletionStages.voidFuture();
+						})
+						.thenAccept(v -> assertNotNull(getSessionFactory().getCurrentSession()))
+				)
+				.thenAccept(v -> assertNull(getSessionFactory().getCurrentSession()))
+		);
+	}
+
+	@Test
+	public void testCurrentStatelessSession(VertxTestContext context) {
+		test( context,
+				getSessionFactory().withStatelessSession(session ->
+						getSessionFactory().withStatelessSession(s -> {
+							assertEquals(session, s);
+							Stage.StatelessSession currentSession = getSessionFactory().getCurrentStatelessSession();
+							assertNotNull(currentSession);
+							assertTrue(currentSession.isOpen());
+							assertEquals(session, currentSession);
+							return CompletionStages.voidFuture();
+						})
+						.thenAccept(v -> assertNotNull(getSessionFactory().getCurrentStatelessSession()))
+				)
+				.thenAccept(v -> assertNull(getSessionFactory().getCurrentStatelessSession()))
+		);
+	}
+
 	private void assertThatPigsAreEqual(GuineaPig expected, GuineaPig actual) {
 		assertNotNull( actual );
 		assertEquals( expected.getId(), actual.getId() );
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java
index 52dc52a7..8dcc07a6 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java
@@ -104,34 +104,6 @@ public void testMutinyUpsert(VertxTestContext context) {
 		);
 	}
 
-	@Test
-	public void testMutinyUpsertWithEntityName(VertxTestContext context) {
-		test( context, getMutinySessionFactory().withStatelessTransaction( ss -> ss
-							  .upsert( Record.class.getName(), new Record( 123L, "hello earth" ) )
-							  .call( () -> ss.upsert( Record.class.getName(), new Record( 456L, "hello mars" ) ) )
-							  .invoke( this::assertQueries )
-					  )
-					  .call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
-									  .createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
-							  .invoke( results -> assertThat( results ).containsExactly(
-									  new Record( 123L, "hello earth" ),
-									  new Record( 456L, "hello mars" )
-							  ) )
-					  )
-					  .call( () -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
-							  .upsert( Record.class.getName(), new Record( 123L, "goodbye earth" ) )
-					  ) )
-					  .invoke( this::assertQueries )
-					  .call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
-									  .createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
-							  .invoke( results -> assertThat( results ).containsExactly(
-									  new Record( 123L, "goodbye earth" ),
-									  new Record( 456L, "hello mars" )
-							  ) )
-					  )
-		);
-	}
-
 	@Test
 	public void testStageUpsert(VertxTestContext context) {
 		test( context, getSessionFactory().withStatelessTransaction( ss -> ss
@@ -160,34 +132,6 @@ public void testStageUpsert(VertxTestContext context) {
 		);
 	}
 
-	@Test
-	public void testStageUpsertWithEntityName(VertxTestContext context) {
-		test( context, getSessionFactory().withStatelessTransaction( ss -> ss
-							  .upsert( Record.class.getName(), new Record( 123L, "hello earth" ) )
-							  .thenCompose( v -> ss.upsert( Record.class.getName(), new Record( 456L, "hello mars" ) ) )
-					  )
-					  .thenAccept( v -> this.assertQueries() )
-					  .thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
-									  .createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
-							  .thenAccept( results -> assertThat( results ).containsExactly(
-									  new Record( 123L, "hello earth" ),
-									  new Record( 456L, "hello mars" )
-							  ) )
-					  )
-					  .thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
-							  .upsert( Record.class.getName(), new Record( 123L, "goodbye earth" ) )
-					  ) )
-					  .thenAccept( v -> this.assertQueries() )
-					  .thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
-									  .createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
-							  .thenAccept( results -> assertThat( results ).containsExactly(
-									  new Record( 123L, "goodbye earth" ),
-									  new Record( 456L, "hello mars" )
-							  ) )
-					  )
-		);
-	}
-
 	private void assertQueries() {
 		if ( hasMergeOperator() ) {
 			assertThat( sqlTracker.getLoggedQueries() ).have( IS_USING_MERGE );
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java
new file mode 100644
index 00000000..2c93d922
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java
@@ -0,0 +1,17 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.delegation;
+
+import org.hibernate.reactive.mutiny.Mutiny;
+import org.hibernate.reactive.mutiny.delegation.MutinySessionDelegator;
+
+@SuppressWarnings("unused")
+class ConcreteSessionDelegator extends MutinySessionDelegator {
+    @Override
+    public Mutiny.Session delegate() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java
new file mode 100644
index 00000000..e275d018
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java
@@ -0,0 +1,17 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.delegation;
+
+import org.hibernate.reactive.mutiny.Mutiny;
+import org.hibernate.reactive.mutiny.delegation.MutinyStatelessSessionDelegator;
+
+@SuppressWarnings("unused")
+class ConcreteStatelessSessionDelegator extends MutinyStatelessSessionDelegator {
+    @Override
+    public Mutiny.StatelessSession delegate() {
+        throw new UnsupportedOperationException();
+    }
+}