Merge pull request #2736

Adding a public getter for Repeat Loop Name
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..f151584
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,14 @@
+<!--
+Thanks for contributing! Reminders:
++ TARGET the earliest branch where you want the change
+    3.6-dev -> 3.6.8 (bugs only)
+    3.7-dev -> 3.7.3 (non-breaking)
+    master  -> 4.0.0
++ Committers will MERGE the PR forward to newer versions
++ ADD entry to the CHANGELOG.asciidoc for the targeted version
+    Do not reference a JIRA number there
++ ADD JIRA number to title and link in description
++ PRs requires 3 +1s from committers OR
+               1 +1 and 7 day wait to merge.
++ MORE details: https://s.apache.org/rtnal
+-->
\ No newline at end of file
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 2a10258..f70c1c1 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -26,9 +26,16 @@
 This release also includes changes from <<release-3-6-8, 3.6.8>>.
 
 * Refactored mutation events registration by moving reusable code from relevant steps to `EventUtil`
-* Open `NoOpBarrierStep` for extensibility (removed `final` keyword)
+* Open `NoOpBarrierStep` for extensibility (removed `final` keyword).
 * Deprecated public constructor for `SeedStrategy` in favor of builder pattern to be consistent with other strategies.
-* Allow specifying a customized Spark app name
+* Allow specifying a customized Spark app name.
+* Added getter method to `CoinStep` for its probability field.
+* Attempted to detect JDK version for Gremlin Console to avoid problems with Java 17 if `neo4j-gremlin` is used.
+* Fixed so that `TrimGlobalStep` and `TrimLocalStep` have the same character control handling as `Ltrim` and `Rtrim`
+* Fix a bug in `MaxLocalStep`, `MinLocalStep`, `MeanLocalStep` and `SumLocalStep` that it throws `NoSuchElementException` when encounters an empty iterator as input.
+* Fix cases where Map keys of incomparable types could panic in `gremlin-go`.
+* Fixed an issue where missing necessary parameters for logging, resulting in '%!x(MISSING)' output in `gremlin-go`.
+* Added getter method to `ConcatStep`, `ConjoinStep`, `SplitGlobalStep` and `SplitLocalStep` for their private fields.
 
 [[release-3-7-2]]
 === TinkerPop 3.7.2 (April 8, 2024)
@@ -98,7 +105,7 @@
 * Moved some TinkerGraph specific transaction tests from `TransactionMultiThreadedTest` to `TinkerTransactionGraphTest`
 * Fixed incorrect read operations in some cases for `TinkerTransactionGraph`.
 * Updated JavaScript tests to check equality on only id and class when comparing elements for consistency with other GLVs.
-* Improved performance for Element comparison by comparing hashCode() prior to doing more expensive checks.
+* Improved performance for `Element` comparison by comparing hashCode() prior to doing more expensive checks.
 
 ==== Bugs
 
@@ -322,7 +329,12 @@
 [[release-3-6-8]]
 === TinkerPop 3.6.8 (NOT OFFICIALLY RELEASED YET)
 
+* Fixed a bug in GremlinServer not properly propagating arguments when authentication is enabled.
 * Fixed bug in Java driver where connection pool was not removing dead connections under certain error conditions.
+* Raised handshake exceptions for Java driver for `NoHostAvailableException` situations.
+* The default logging level for Gremlin Console in Windows is set to the same WARN level as for Linux.
+* Updated to Docker Compose V2 with `docker-compose` changed to `docker compose` in pom and script files.
+* Add command line option `-l` to change logging level for Gremlin Console in Windows.
 
 [[release-3-6-7]]
 === TinkerPop 3.6.7 (April 8, 2024)
diff --git a/docker/build.sh b/docker/build.sh
index 1b299d5..d495ff5 100755
--- a/docker/build.sh
+++ b/docker/build.sh
@@ -106,7 +106,7 @@
 
 function check_status {
   status=$?
-  [ "$1" == "down" ] && docker-compose down
+  [ "$1" == "down" ] && docker compose down
   popd > /dev/null
   [ $status -ne 0 ] && exit $status
 }
@@ -128,7 +128,7 @@
 check_status
 
 if [ -n "${RUN_TESTS}" ]; then
-  # If testing, then build base server which is required by the following docker-compose.
+  # If testing, then build base server which is required by the following docker compose.
   pushd ${ABS_PROJECT_HOME}/gremlin-server > /dev/null
   docker build -f ./Dockerfile --build-arg GREMLIN_SERVER_DIR=target/apache-tinkerpop-gremlin-server-${GREMLIN_SERVER}-standalone -t tinkerpop/gremlin-server:${GREMLIN_SERVER} .
   check_status
@@ -136,7 +136,7 @@
 
 if [ -n "${INCLUDE_GO}" ] && [ -n "${RUN_TESTS}" ]; then
   pushd ${ABS_PROJECT_HOME}/gremlin-go > /dev/null
-  docker-compose up --build --exit-code-from gremlin-go-integration-tests
+  docker compose up --build --exit-code-from gremlin-go-integration-tests
   check_status "down"
 fi
 
@@ -145,19 +145,19 @@
   export BUILD_DIR=$(pwd)/target/python3/
   mkdir -p ${BUILD_DIR}
   cp -r ./src/main/python/* ${BUILD_DIR}
-  docker-compose up --build --abort-on-container-exit gremlin-server-test-python gremlin-python-integration-tests
+  docker compose up --build --abort-on-container-exit gremlin-server-test-python gremlin-python-integration-tests
   check_status "down"
 fi
 
 if [ -n "${INCLUDE_DOTNET}" ] && [ -n "${RUN_TESTS}" ]; then
   pushd ${ABS_PROJECT_HOME}/gremlin-dotnet/test > /dev/null
-  docker-compose up --build --exit-code-from gremlin-dotnet-integration-tests
+  docker compose up --build --exit-code-from gremlin-dotnet-integration-tests
   check_status "down"
 fi
 
 if [ -n "${INCLUDE_JAVASCRIPT}" ] && [ -n "${RUN_TESTS}" ]; then
   pushd ${ABS_PROJECT_HOME}/gremlin-javascript/src/main/javascript/gremlin-javascript > /dev/null
-  docker-compose up --build --exit-code-from gremlin-js-integration-tests
+  docker compose up --build --exit-code-from gremlin-js-integration-tests
   check_status "down"
 fi
 
diff --git a/docs/src/dev/developer/development-environment.asciidoc b/docs/src/dev/developer/development-environment.asciidoc
index 3026abe..d9f8ab4 100644
--- a/docs/src/dev/developer/development-environment.asciidoc
+++ b/docs/src/dev/developer/development-environment.asciidoc
@@ -352,7 +352,7 @@
 Docker allows you to test the driver without installing any dependencies. The following command can be used to run docker:
 
 [source,text]
-docker-compose up --exit-code-from gremlin-go-integration-tests
+docker compose up --exit-code-from gremlin-go-integration-tests
 
 See the <<release-environment,Release Environment>> section for more information on release manager configurations.
 
@@ -635,12 +635,12 @@
 
 * Run Maven commands, e.g. `mvn clean install` inside of project folder e.g. `tinkerpop/gremlin-go`, or `mvn clean install -pl gremlin-go`
 inside of `tinkerpop` (platform-agnostic - recommended)
-* Add `GREMLIN_SERVER=<server-image-version>` and `HOME=<user-home-directory>` to an `.env` file inside project folder and run `docker-compose up --exit-code-from gremlin-go-integration-tests` (Platform-agnostic).
-* Run `GREMLIN_SERVER=<server-image-version> docker-compose up --exit-code-from gremlin-go-integration-tests` in Unix/Linux.
-* Run `$env:GREMLIN_SERVER="<server-image-version>";$env:HOME=$env:USERPROFILE;docker-compose up --exit-code-from gremlin-go-integration-tests` in Windows PowerShell.
+* Add `GREMLIN_SERVER=<server-image-version>` and `HOME=<user-home-directory>` to an `.env` file inside project folder and run `docker compose up --exit-code-from gremlin-go-integration-tests` (Platform-agnostic).
+* Run `GREMLIN_SERVER=<server-image-version> docker compose up --exit-code-from gremlin-go-integration-tests` in Unix/Linux.
+* Run `$env:GREMLIN_SERVER="<server-image-version>";$env:HOME=$env:USERPROFILE;docker compose up --exit-code-from gremlin-go-integration-tests` in Windows PowerShell.
 
-You should see exit code 0 upon successful completion of the test suites. Run `docker-compose down` to remove the
-service containers (not needed if you executed Maven commands or `run.sh`), or `docker-compose down --rmi all` to
+You should see exit code 0 upon successful completion of the test suites. Run `docker compose down` to remove the
+service containers (not needed if you executed Maven commands or `run.sh`), or `docker compose down --rmi all` to
 remove the service containers while deleting all used images.
 
 Note for running docker with MacOS on ARM processors: Docker's performance is extremely poor on ARM Mac's in its
diff --git a/docs/src/dev/developer/release.asciidoc b/docs/src/dev/developer/release.asciidoc
index 6a5e525..9812c79 100644
--- a/docs/src/dev/developer/release.asciidoc
+++ b/docs/src/dev/developer/release.asciidoc
@@ -324,6 +324,7 @@
 .. `bin/process-docs.sh` and validate the generated `SNAPSHOT` documentation locally and then `bin/publish-docs.sh <username>`
 .. Commit and push the `SNAPSHOT` changes to git
 . Examine the `future.asciidoc` and update the "Roadmap" as needed.
+. Update the version numbers in `pull_request_template.md`.
 . Send email to advise that code freeze is lifted.
 . Consider the changes made to Gremlin and determine if the community needs to organize a PR to [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/gremlin) to keep TypeScript up to date.
 . Ensure that the GLV examples compile and run with the latest image and dependencies: `bin/run-examples.sh`.
diff --git a/docs/src/reference/implementations-neo4j.asciidoc b/docs/src/reference/implementations-neo4j.asciidoc
index e15b008..cf61846 100644
--- a/docs/src/reference/implementations-neo4j.asciidoc
+++ b/docs/src/reference/implementations-neo4j.asciidoc
@@ -23,6 +23,9 @@
 be dropped from future versions of TinkerPop if compatibility cannot reasonably be maintained. Alternative TinkerPop
 enabled graph providers can be found on the link:https://tinkerpop.apache.org/providers.html[TinkerPop site].
 
+WARNING: Neo4j-Gremlin can work with JDK17, but requires the use of the `--add-opens` flag to be provided to the JVM
+as follows: `--add-opens=java.base/sun.nio.ch=ALL-UNNAMED`.
+
 [source,xml]
 ----
 <dependency>
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc
index 9ddac7c..4dda8ba 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -30,7 +30,6 @@
 
 === Upgrading for Users
 
-
 == TinkerPop 3.7.2
 
 *Release Date: April 8, 2024*
@@ -38,18 +37,6 @@
 Please see the link:https://github.com/apache/tinkerpop/blob/3.7.2/CHANGELOG.asciidoc#release-3-7-2[changelog] for a
 complete list of all the modifications that are part of this release.
 
-=== Upgrading for Users
-
-==== Notice: Renaming `none()` step to `discard()` in 4.0.0
-`none()`, which is primarily used by `iterate()` to discard traversal results in remote contexts, will be renamed to
-`discard()` in release 4.0.0.
-
-=== Upgrading for Providers
-
-==== Notice: Renaming NoneStep to DiscardStep in 4.0.0
-NoneStep, which is primarily used by `iterate()` to discard traversal results in remote contexts, will be renamed to
-DiscardStep in release 4.0.0 to make room for a list filtering NoneStep.
-
 == TinkerPop 3.7.1
 *Release Date: November 20, 2023*
 
@@ -59,8 +46,9 @@
 === Upgrading for Users
 
 ==== String Manipulation Steps
-This version introduces the following new string manipulation steps `asString()`, `length()`, `toLower()`, `toUpper()`, `trim()`,
-`lTrim()`, `rTrim()`, `reverse()`, `replace()`, `split()`, `substring()`, and `format()`, as well as modifications to the `concat()` step introduced in 3.7.0.
+This version introduces the following new string manipulation steps `asString()`, `length()`, `toLower()`, `toUpper()`,
+`trim()`, `lTrim()`, `rTrim()`, `reverse()`, `replace()`, `split()`, `substring()`, and `format()`, as well as
+modifications to the `concat()` step introduced in 3.7.0.
 
 ===== Updates to String Step concat():
 Concat has been modified to take traversal varargs instead of a single traversal. Users no longer have to chain
@@ -73,7 +61,10 @@
 ==>markoknowsjosh
 ----
 
-A notable breaking change from 3.7.0 is that we have output order of `inject()` as a child of `concat()` to be consistent with other parent steps. Any 3.7.0 uses of `concat(inject(X))` should change to `concat(constant(X))` to retain the old semantics.
+A notable breaking change from 3.7.0 is that we have output order of `inject()` as a child of `concat()` to be
+consistent with other parent steps. Any 3.7.0 uses of `concat(inject(X))` should change to `concat(constant(X))` to
+retain the old semantics.
+
 [source,text]
 ----
 // 3.7.0
diff --git a/gremlin-console/src/main/bin/gremlin-java8.bat b/gremlin-console/src/main/bin/gremlin-java8.bat
index a80aa6f..227633f 100644
--- a/gremlin-console/src/main/bin/gremlin-java8.bat
+++ b/gremlin-console/src/main/bin/gremlin-java8.bat
@@ -40,9 +40,20 @@
 )
 cd ..
 
+set GREMLIN_LOG_LEVEL=WARN
+
+:: Process options
+
+:parse
+IF "%~1"=="" GOTO endparse
+IF "%~1"=="-l" set GREMLIN_LOG_LEVEL=%~2
+SHIFT
+GOTO parse
+:endparse
+
 :: workaround for https://issues.apache.org/jira/browse/GROOVY-6453
 set JAVA_OPTIONS=-Xms32m -Xmx512m -Djline.terminal=none
 
 :: Launch the application
 
-java %JAVA_OPTIONS% %JAVA_ARGS% -cp "%CONSOLE_JARS%" org.apache.tinkerpop.gremlin.console.Console %*
+java %JAVA_OPTIONS% %JAVA_ARGS% -cp "%CONSOLE_JARS%" "-Dlogback.configurationFile=conf/logback.xml" "-Dgremlin.logback.level=%GREMLIN_LOG_LEVEL%" org.apache.tinkerpop.gremlin.console.Console %*
diff --git a/gremlin-console/src/main/bin/gremlin.bat b/gremlin-console/src/main/bin/gremlin.bat
index 021e37b..739635e 100644
--- a/gremlin-console/src/main/bin/gremlin.bat
+++ b/gremlin-console/src/main/bin/gremlin.bat
@@ -34,11 +34,22 @@
 
 cd ..
 
+set GREMLIN_LOG_LEVEL=WARN
+
+:: Process options
+
+:parse
+IF "%~1"=="" GOTO endparse
+IF "%~1"=="-l" set GREMLIN_LOG_LEVEL=%~2
+SHIFT
+GOTO parse
+:endparse
+
 :: workaround for https://issues.apache.org/jira/browse/GROOVY-6453
 set JAVA_OPTIONS=-Xms32m -Xmx512m -Djline.terminal=none
 
 :: Launch the application
 
-java %JAVA_OPTIONS% %JAVA_ARGS% -cp "%LIBDIR%\*;%EXTDIR%;" org.apache.tinkerpop.gremlin.console.Console %*
+java %JAVA_OPTIONS% %JAVA_ARGS% -cp "%LIBDIR%\*;%EXTDIR%;" "-Dlogback.configurationFile=conf/logback.xml" "-Dgremlin.logback.level=%GREMLIN_LOG_LEVEL%" org.apache.tinkerpop.gremlin.console.Console %*
 
 set CLASSPATH=%OLD_CLASSPATH%
\ No newline at end of file
diff --git a/gremlin-console/src/main/bin/gremlin.sh b/gremlin-console/src/main/bin/gremlin.sh
index 9fbd8a0..36dcb4d 100755
--- a/gremlin-console/src/main/bin/gremlin.sh
+++ b/gremlin-console/src/main/bin/gremlin.sh
@@ -108,6 +108,12 @@
     set -x
 fi
 
+# Try to detect JDK version and add specific flag for JDK 17 to allow use of neo4j-gremlin
+JAVA_VERSION=$($JAVA -version 2>&1 | awk -F '"' '/version/ {print $2}')
+if [[ "$JAVA_VERSION" == 17* ]]; then
+    JVM_OPTS+=( "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED" )
+fi
+
 # Start the JVM, execute the application, and return its exit code
 # shellcheck disable=SC2068
 exec $JAVA ${JVM_OPTS[@]} org.apache.tinkerpop.gremlin.console.Console "$@"
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
index a7d86cb..e4a44ab 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
@@ -46,6 +46,10 @@
         random.setSeed(seed);
     }
 
+    public double getProbability() {
+        return this.probability;
+    }
+
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
         long newBulk = 0l;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConcatStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConcatStep.java
index 04811d8..86d737f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConcatStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConcatStep.java
@@ -120,6 +120,14 @@
         return this.isNullString? null : sb.toString();
     }
 
+    public List<String> getConcatStrings() {
+        return this.concatStrings;
+    }
+
+    public String getStringArgsResult() {
+        return this.stringArgsResult;
+    }
+
     @Override
     public Set<TraverserRequirement> getRequirements() {
         return this.getSelfAndChildRequirements(TraverserRequirement.OBJECT);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java
index 0f38739..ce6818b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java
@@ -66,6 +66,10 @@
         }
     }
 
+    public String getDelimiter() {
+        return this.delimiter;
+    }
+
     @Override
     public Set<TraverserRequirement> getRequirements() { return Collections.singleton(TraverserRequirement.OBJECT); }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimGlobalStep.java
index 84dbd5c..8ce2104 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimGlobalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimGlobalStep.java
@@ -25,6 +25,8 @@
 import java.util.Collections;
 import java.util.Set;
 
+import static org.apache.tinkerpop.gremlin.process.traversal.step.util.StringLocalStep.getTrimmedString;
+
 /**
  * Reference implementation for lTrim() step, a mid-traversal step which returns a string with leading
  * whitespace removed. Null values are not processed and remain as null when returned.
@@ -49,15 +51,7 @@
         }
 
         // we will pass null values to next step
-        if (null == item)
-                return null;
-
-        int i = 0;
-        while (i < ((String) item).length() && Character.isWhitespace(((String) item).charAt(i))) {
-            i++;
-        }
-
-        return (E) ((String) item).substring(i);
+        return (E) getTrimmedString((String) item, true, false);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java
index 1592067..db12f62 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java
@@ -51,18 +51,9 @@
 
     @Override
     protected E applyStringOperation(String item) {
-        return (E) item.substring(getIdx(item));
+        return (E) getTrimmedString(item, true, false);
     }
 
     @Override
     public String getStepName() { return "lTrim(local)"; }
-
-    private int getIdx(final String str) {
-        int idx = 0;
-        while (idx < str.length() && Character.isWhitespace(str.charAt(idx))) {
-            idx++;
-        }
-        return idx;
-    }
-
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
index e4ad8b4..93b95e4 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
@@ -21,11 +21,11 @@
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 import static org.apache.tinkerpop.gremlin.util.NumberHelper.max;
@@ -34,23 +34,26 @@
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class MaxLocalStep<E extends Comparable, S extends Iterable<E>> extends ScalarMapStep<S, E> {
+public final class MaxLocalStep<E extends Comparable, S extends Iterable<E>> extends MapStep<S, E> {
 
     public MaxLocalStep(final Traversal.Admin traversal) {
         super(traversal);
     }
 
     @Override
-    protected E map(final Traverser.Admin<S> traverser) {
-        final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
-        if (iterator.hasNext()) {
-            Comparable result = iterator.next();
-            while (iterator.hasNext()) {
-                result = max(iterator.next(), result);
+    protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+        while (true) {
+            final Traverser.Admin<S> traverser = this.starts.next();
+            final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+            if (iterator.hasNext()) {
+                Comparable result = iterator.next();
+                while (iterator.hasNext()) {
+                    result = max(iterator.next(), result);
+                }
+                return traverser.split((E) result, this);
             }
-            return (E) result;
         }
-        throw FastNoSuchElementException.instance();
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
index 7a5abe7..d6e30e3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
@@ -18,44 +18,49 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
-import org.apache.tinkerpop.gremlin.util.NumberHelper;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.add;
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.div;
+
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class MeanLocalStep<E extends Number, S extends Iterable<E>> extends ScalarMapStep<S, Number> {
+public final class MeanLocalStep<E extends Number, S extends Iterable<E>> extends MapStep<S, E> {
 
     public MeanLocalStep(final Traversal.Admin traversal) {
         super(traversal);
     }
 
     @Override
-    protected Number map(final Traverser.Admin<S> traverser) {
-        final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
-        if (iterator.hasNext()) {
-            // forward the iterator to the first non-null or return null
-            E result = untilNonNull(iterator);
-            Long counter = 1L;
-            while (iterator.hasNext()) {
-                final Number n = iterator.next();
-                if (n != null) {
-                    result = (E) NumberHelper.add(result, n);
-                    counter++;
+    protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+        while (true) {
+            final Traverser.Admin<S> traverser = this.starts.next();
+            final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+            if (iterator.hasNext()) {
+                // forward the iterator to the first non-null or return null
+                E result = untilNonNull(iterator);
+                Long counter = 1L;
+                while (iterator.hasNext()) {
+                    final Number n = iterator.next();
+                    if (n != null) {
+                        result = (E) add(result, n);
+                        counter++;
+                    }
                 }
+                return traverser.split((E) div(result, counter, true), this);
             }
-            return NumberHelper.div(result, counter, true);
         }
-        throw FastNoSuchElementException.instance();
     }
 
     private E untilNonNull(final Iterator<E> itty) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
index 0781d4a..cf29a73 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
@@ -21,11 +21,11 @@
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 import static org.apache.tinkerpop.gremlin.util.NumberHelper.min;
@@ -34,23 +34,26 @@
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class MinLocalStep<E extends Comparable, S extends Iterable<E>> extends ScalarMapStep<S, E> {
+public final class MinLocalStep<E extends Comparable, S extends Iterable<E>> extends MapStep<S, E> {
 
     public MinLocalStep(final Traversal.Admin traversal) {
         super(traversal);
     }
 
     @Override
-    protected E map(final Traverser.Admin<S> traverser) {
-        final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
-        if (iterator.hasNext()) {
-            Comparable result = iterator.next();
-            while (iterator.hasNext()) {
-                result = min(iterator.next(), result);
+    protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+        while (true) {
+            final Traverser.Admin<S> traverser = this.starts.next();
+            final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+            if (iterator.hasNext()) {
+                Comparable result = iterator.next();
+                while (iterator.hasNext()) {
+                    result = min(iterator.next(), result);
+                }
+                return traverser.split((E) result, this);
             }
-            return (E) result;
         }
-        throw FastNoSuchElementException.instance();
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimGlobalStep.java
index aae0c50..3e52446 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimGlobalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimGlobalStep.java
@@ -25,6 +25,8 @@
 import java.util.Collections;
 import java.util.Set;
 
+import static org.apache.tinkerpop.gremlin.process.traversal.step.util.StringLocalStep.getTrimmedString;
+
 /**
  * Reference implementation for rTrim() step, a mid-traversal step which a string with trailing
  * whitespace removed. Null values are not processed and remain as null when returned.
@@ -49,15 +51,7 @@
         }
 
         // we will pass null values to next step
-        if (null == item)
-                return null;
-
-        int i = ((String) item).length() - 1;
-        while (i >= 0 && Character.isWhitespace(((String) item).charAt(i))) {
-            i--;
-        }
-
-        return (E) ((String) item).substring(0,i+1);
+        return (E) getTrimmedString((String) item, false, true);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimLocalStep.java
index 1b1182d..ee7c39c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimLocalStep.java
@@ -37,18 +37,9 @@
 
     @Override
     protected E applyStringOperation(String item) {
-        return (E) item.substring(0,getEndIdx(item)+1);
+        return (E) getTrimmedString(item, false, true);
     }
 
     @Override
     public String getStepName() { return "rTrim(local)"; }
-
-    private int getEndIdx(final String str) {
-        int idx = str.length() - 1;
-        while (idx >= 0 && Character.isWhitespace(str.charAt(idx))) {
-            idx--;
-        }
-        return idx;
-    }
-
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitGlobalStep.java
index 2465e72..14e886e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitGlobalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitGlobalStep.java
@@ -60,6 +60,10 @@
         return null == item? null : (E) Arrays.asList(StringUtils.splitByWholeSeparator((String) item, this.separator));
     }
 
+    public String getSeparator() {
+        return this.separator;
+    }
+
     @Override
     public Set<TraverserRequirement> getRequirements() {
         return Collections.singleton(TraverserRequirement.OBJECT);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitLocalStep.java
index 61a9901..5d638c7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitLocalStep.java
@@ -56,6 +56,10 @@
     @Override
     public String getStepName() { return "split(local)"; }
 
+    public String getSeparator() {
+        return this.separator;
+    }
+
     @Override
     public int hashCode() {
         int result = super.hashCode();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
index 92b9796..69fd944 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
@@ -18,39 +18,47 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
-import org.apache.tinkerpop.gremlin.util.NumberHelper;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.add;
+
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class SumLocalStep<E extends Number, S extends Iterable<E>> extends ScalarMapStep<S, E> {
+public final class SumLocalStep<E extends Number, S extends Iterable<E>> extends MapStep<S, E> {
 
     public SumLocalStep(final Traversal.Admin traversal) {
         super(traversal);
     }
 
     @Override
-    protected E map(final Traverser.Admin<S> traverser) {
-        final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
-        if (iterator.hasNext()) {
-            // forward the iterator to the first non-null or return null
-            Number result = untilNonNull(iterator);
-            while (iterator.hasNext()) {
-                result = NumberHelper.add(result, iterator.next());
+    protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+        while (true) {
+            final Traverser.Admin<S> traverser = this.starts.next();
+            final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+            if (iterator.hasNext()) {
+                // forward the iterator to the first non-null or return null
+                E result = untilNonNull(iterator);
+                while (iterator.hasNext()) {
+                    final Number n = iterator.next();
+                    if (n != null) {
+                        result = (E) add(result, n);
+                    }
+                }
+                return traverser.split(result, this);
             }
-            return (E) result;
+
         }
-        throw FastNoSuchElementException.instance();
     }
 
     private E untilNonNull(final Iterator<E> itty) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimGlobalStep.java
index 72f2a41..a543e2b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimGlobalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimGlobalStep.java
@@ -25,6 +25,8 @@
 import java.util.Collections;
 import java.util.Set;
 
+import static org.apache.tinkerpop.gremlin.process.traversal.step.util.StringLocalStep.getTrimmedString;
+
 /**
  * Reference implementation for trim() step, a mid-traversal step which returns a string with leading and trailing
  * whitespace removed. Null values are not processed and remain as null when returned.
@@ -47,9 +49,7 @@
             throw new IllegalArgumentException(
                     String.format("The trim() step can only take string as argument, encountered %s", item.getClass()));
         }
-
-        // we will pass null values to next step
-        return null == item? null : (E) ((String) item).trim();
+        return (E) getTrimmedString((String) item, true, true);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimLocalStep.java
index dd6752f..4b35734 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimLocalStep.java
@@ -37,7 +37,7 @@
 
     @Override
     protected E applyStringOperation(String item) {
-        return (E) item.trim();
+        return (E) getTrimmedString(item, true, true);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
index 48741f8..1eb3f4b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
@@ -48,6 +48,10 @@
         this.metadata = metadata;
     }
 
+    public String getMessage() {
+        return message;
+    }
+
     @Override
     protected void sideEffect(final Traverser.Admin<S> traverser) {
         throw new FailException(traversal, traverser, message, metadata);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StringLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StringLocalStep.java
index d27a454..37f4a69 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StringLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StringLocalStep.java
@@ -72,6 +72,27 @@
         return Collections.singleton(TraverserRequirement.OBJECT);
     }
 
+    public static String getTrimmedString(final String s, final boolean lTrim, final boolean rTrim) {
+        if (s == null) {
+            return null;
+        }
+
+        int start = 0;
+        if (lTrim) {
+            while (start < s.length() && Character.isWhitespace(s.charAt(start))) {
+                start++;
+            }
+        }
+
+        int end = s.length() - 1;
+        if (rTrim) {
+            while (end >= start && Character.isWhitespace(s.charAt(end))) {
+                end--;
+            }
+        }
+        return s.substring(start, end + 1);
+    }
+
     protected abstract E applyStringOperation(final String item);
 
     protected abstract String getStepName();
diff --git a/gremlin-dotnet/docker-compose.yml b/gremlin-dotnet/docker-compose.yml
index 7c84aa0..7f5efab 100644
--- a/gremlin-dotnet/docker-compose.yml
+++ b/gremlin-dotnet/docker-compose.yml
@@ -15,8 +15,6 @@
 #    specific language governing permissions and limitations
 #    under the License.
 
-version: "3.8"
-
 services:
 
   gremlin-server-test-dotnet:
diff --git a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
index 2efe138..2ba0c61 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
+++ b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
@@ -74,8 +74,8 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
-    <PackageReference Include="System.Text.Json" Version="8.0.3" />
-    <PackageReference Include="Polly" Version="8.4.0" />
+    <PackageReference Include="System.Text.Json" Version="8.0.4" />
+    <PackageReference Include="Polly" Version="8.4.1" />
   </ItemGroup>
     
   <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index b10a2db..4be4287 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -749,7 +749,8 @@
                {"g_injectXa_null_bX_intersectXa_cX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Intersect(p["xx2"])}}, 
                {"g_injectXa_null_bX_intersectXa_null_cX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Intersect(p["xx2"])}}, 
                {"g_injectX3_threeX_intersectXfive_three_7X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Intersect(p["xx2"])}}, 
-               {"g_injectX__feature___test__nullX_lTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("  feature"," one test",null,""," ").LTrim()}}, 
+               {"g_injectX__feature___test__nullX_lTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("  feature"," one test",null,""," ","\u3000abc","abc\u3000","\u3000abc\u3000","\u3000\u3000").LTrim()}}, 
+               {"g_injectX__feature___test__nullX_lTrimXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(new List<object> {"  feature  ", " one test ", null, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"}).LTrim<object>(Scope.Local)}}, 
                {"g_injectX__feature__X_lTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject("  feature  ").LTrim()}}, 
                {"g_injectXListXa_bXX_lTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).LTrim()}}, 
                {"g_injectXListX1_2XX_lTrimXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).LTrim<object>(Scope.Local)}}, 
@@ -834,6 +835,7 @@
                {"g_V_repeatXbothX_timesX5X_age_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Repeat(__.Both()).Times(5).Values<object>("age").Max<object>()}}, 
                {"g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Group<object,object>().By("name").By(__.BothE().Values<object>("weight").Max<object>())}}, 
                {"g_VX1X_valuesXageX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Max<object>(Scope.Local)}}, 
+               {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Max<object>(Scope.Local)}}, 
                {"g_V_age_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Mean<object>()}}, 
                {"g_V_foo_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Mean<object>()}}, 
                {"g_V_age_fold_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold().Mean<object>(Scope.Local)}}, 
@@ -850,6 +852,7 @@
                {"g_injectXnull_10_20_nullX_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,p["xx1"],p["xx2"],null).Mean<object>()}}, 
                {"g_injectXlistXnull_10_20_nullXX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Mean<object>(Scope.Local)}}, 
                {"g_VX1X_valuesXageX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Mean<object>(Scope.Local)}}, 
+               {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Mean<object>(Scope.Local)}}, 
                {"g_injectXnullX_mergeXinjectX1XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null).Merge(__.Inject(1))}}, 
                {"g_V_valuesXnameX_mergeXV_foldX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Merge(__.V().Fold())}}, 
                {"g_V_fold_mergeXconstantXnullXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fold().Merge(__.Constant<object>(null))}}, 
@@ -995,6 +998,7 @@
                {"g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Group<object,object>().By("name").By(__.BothE().Values<object>("weight").Min<object>())}}, 
                {"g_V_foo_injectX9999999999X_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Inject(p["xx1"]).Min<object>()}}, 
                {"g_VX1X_valuesXageX_minXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Min<object>(Scope.Local)}}, 
+               {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Min<object>(Scope.Local)}}, 
                {"g_V_name_order", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order()}}, 
                {"g_V_name_order_byXa1_b1X_byXb2_a2X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order().By((IComparator) p["c1"]).By((IComparator) p["c2"])}}, 
                {"g_V_order_byXname_ascX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By("name",Order.Asc).Values<object>("name")}}, 
@@ -1075,7 +1079,8 @@
                {"g_V_hasXageX_propertiesXage_nameX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age").Properties<object>("age","name").Value<object>()}}, 
                {"g_V_propertiesXname_age_nullX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>("name","age",null).Value<object>()}}, 
                {"g_V_valuesXname_age_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name","age",null)}}, 
-               {"g_injectX__feature___test__nullX_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("feature  ","one test ",null,""," ").RTrim()}}, 
+               {"g_injectX__feature___test__nullX_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("feature  ","one test ",null,""," ","\u3000abc","abc\u3000","\u3000abc\u3000","\u3000\u3000").RTrim()}}, 
+               {"g_injectX__feature___test__nullX_rTrimXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(new List<object> {"  feature  ", " one test ", null, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"}).RTrim<object>(Scope.Local)}}, 
                {"g_injectX__feature__X_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject("  feature  ").RTrim()}}, 
                {"g_injectXListXa_bXX_rTrim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).RTrim()}}, 
                {"g_injectXListX1_2XX_rTrimXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).RTrim<object>(Scope.Local)}}, 
@@ -1212,6 +1217,7 @@
                {"g_injectXnull_10_5_nullX_sum", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,p["xx1"],p["xx2"],null).Sum<object>()}}, 
                {"g_injectXlistXnull_10_5_nullXX_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Sum<object>(Scope.Local)}}, 
                {"g_VX1X_valuesXageX_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Sum<object>(Scope.Local)}}, 
+               {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Sum<object>(Scope.Local)}}, 
                {"g_injectXfeature_test_nullX_toLower", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("FEATURE","tESt",null).ToLower()}}, 
                {"g_injectXfeature_test_nullX_toLowerXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).ToLower<object>(Scope.Local)}}, 
                {"g_injectXListXa_bXX_toLower", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).ToLower()}}, 
@@ -1225,7 +1231,8 @@
                {"g_V_valuesXnameX_toUpper", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").ToUpper()}}, 
                {"g_V_valuesXnameX_toUpperXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").ToUpper<object>(Scope.Local)}}, 
                {"g_V_valuesXnameX_order_fold_toUpperXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order().Fold().ToUpper<object>(Scope.Local)}}, 
-               {"g_injectX__feature___test__nullX_trim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("  feature  "," one test ",null,""," ").Trim()}}, 
+               {"g_injectX__feature___test__nullX_trim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("  feature  "," one test ",null,""," ","\u3000abc","abc\u3000","\u3000abc\u3000","\u3000\u3000").Trim()}}, 
+               {"g_injectX__feature___test__nullX_trimXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(new List<object> {"  feature  ", " one test ", null, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"}).Trim<object>(Scope.Local)}}, 
                {"g_injectXListXa_bXX_trim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Trim()}}, 
                {"g_injectXListX1_2XX_trimXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Trim<object>(Scope.Local)}}, 
                {"g_V_valuesXnameX_trim", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name"," marko ").Property("age",29).As("marko").AddV("person").Property("name","  vadas  ").Property("age",27).As("vadas").AddV("software").Property("name","  lop").Property("lang","java").As("lop").AddV("person").Property("name","josh  ").Property("age",32).As("josh").AddV("software").Property("name","   ripple   ").Property("lang","java").As("ripple").AddV("person").Property("name","peter").Property("age",35).As("peter").AddE("knows").From("marko").To("vadas").Property("weight",0.5).AddE("knows").From("marko").To("josh").Property("weight",1.0).AddE("created").From("marko").To("lop").Property("weight",0.4).AddE("created").From("josh").To("ripple").Property("weight",1.0).AddE("created").From("josh").To("lop").Property("weight",0.4).AddE("created").From("peter").To("lop").Property("weight",0.2), (g,p) =>g.V().Values<object>("name").Trim()}}, 
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
index b867b67..4e923b7 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
@@ -19,8 +19,8 @@
     <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
     <PackageReference Include="NSubstitute" Version="5.1.0" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
-    <PackageReference Include="xunit" Version="2.8.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
+    <PackageReference Include="xunit" Version="2.9.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
     <PackageReference Include="YamlDotNet" Version="12.2.0" />
   </ItemGroup>
diff --git a/gremlin-dotnet/test/Gremlin.Net.Template.IntegrationTest/Gremlin.Net.Template.IntegrationTest.csproj b/gremlin-dotnet/test/Gremlin.Net.Template.IntegrationTest/Gremlin.Net.Template.IntegrationTest.csproj
index afb832b..6e6013d 100644
--- a/gremlin-dotnet/test/Gremlin.Net.Template.IntegrationTest/Gremlin.Net.Template.IntegrationTest.csproj
+++ b/gremlin-dotnet/test/Gremlin.Net.Template.IntegrationTest/Gremlin.Net.Template.IntegrationTest.csproj
@@ -7,8 +7,8 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
-    <PackageReference Include="xunit" Version="2.8.1" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
+    <PackageReference Include="xunit" Version="2.9.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Gremlin.Net.UnitTest.csproj b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Gremlin.Net.UnitTest.csproj
index d919ac8..4681f19 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Gremlin.Net.UnitTest.csproj
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Gremlin.Net.UnitTest.csproj
@@ -15,8 +15,8 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
     <PackageReference Include="NSubstitute" Version="5.1.0" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
-    <PackageReference Include="xunit" Version="2.8.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
+    <PackageReference Include="xunit" Version="2.9.0" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/gremlin-dotnet/test/pom.xml b/gremlin-dotnet/test/pom.xml
index cc12323..e36e893 100644
--- a/gremlin-dotnet/test/pom.xml
+++ b/gremlin-dotnet/test/pom.xml
@@ -102,8 +102,9 @@
                                         <!-- setting this env variable is needed to be cross-platform compatible -->
                                         <HOME>${user.home}</HOME>
                                     </environmentVariables>
-                                    <executable>docker-compose</executable>
+                                    <executable>docker</executable>
                                     <arguments>
+                                        <argument>compose</argument>
                                         <argument>up</argument>
                                         <argument>--build</argument>
                                         <argument>--exit-code-from</argument>
@@ -120,8 +121,9 @@
                                 <configuration>
                                     <skip>${skipTests}</skip>
                                     <!-- don't need to set env variables for container tear down -->
-                                    <executable>docker-compose</executable>
+                                    <executable>docker</executable>
                                     <arguments>
+                                        <argument>compose</argument>
                                         <argument>down</argument>
                                     </arguments>
                                 </configuration>
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Client.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Client.java
index 7ce5c40..b53f1cd 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Client.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Client.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.driver;
 
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakeException;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.tinkerpop.gremlin.util.Tokens;
 import org.slf4j.Logger;
@@ -557,7 +558,8 @@
         private void throwNoHostAvailableException() {
             final Throwable rootCause = ExceptionUtils.getRootCause(initializationFailure);
             // allow the certain exceptions to propagate as a cause
-            if (rootCause instanceof SSLException || rootCause instanceof ConnectException) {
+            if (rootCause instanceof SSLException || rootCause instanceof ConnectException ||
+                    rootCause instanceof WebSocketClientHandshakeException) {
                 throw new NoHostAvailableException(initializationFailure);
             } else {
                 throw new NoHostAvailableException();
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java
index 4259e25..898f3b1 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java
@@ -30,7 +30,9 @@
 import java.io.File;
 import java.io.FileWriter;
 import java.io.PrintWriter;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
@@ -48,7 +50,7 @@
  */
 public class ProfilingApplication {
 
-    private static final Random random = new Random();
+    private static final Random random = new Random(0); // Same seed to ensure consistent test runs.
     private static final String[] scripts = new String[]{
             "g.V()",
             "g.V(1).out('knows')",
@@ -87,7 +89,7 @@
         this.exercise = exercise;
     }
 
-    public long execute() throws Exception {
+    public long executeThroughput() throws Exception {
         final AtomicInteger tooSlow = new AtomicInteger(0);
 
         final Client client = cluster.connect();
@@ -130,16 +132,51 @@
         }
     }
 
+    public double executeLatency() throws Exception {
+        final Client client = cluster.connect();
+        final String executionId = "[" + executionName + "]";
+        try {
+            client.init();
+
+            final long start = System.nanoTime();
+            int size = 0;
+            final Iterator itr = client.submitAsync(script).get().iterator();
+            try {
+                while (true) {
+                    itr.next();
+                    size++;
+                }
+            } catch (NoSuchElementException nsee) {
+                ; // Expected as hasNext() not called to increase performance.
+            }
+            final long end = System.nanoTime();
+            final long total = (end - start);
+
+
+            final double totalSeconds = total / 1000000000d;
+            System.out.println(String.format(StringUtils.rightPad(executionId, 10) + "time: %s, result count: %s", StringUtils.rightPad(String.valueOf(totalSeconds), 7), StringUtils.rightPad(String.valueOf(size), 10)));
+            return totalSeconds;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            throw new RuntimeException(ex);
+        } finally {
+            client.close();
+        }
+    }
+
     private String chooseScript() {
         return scripts[random.nextInt(scripts.length - 1)];
     }
 
+    public enum TestType { LATENCY, THROUGHPUT };
+
     public static void main(final String[] args) {
         final Map<String,Object> options = ElementHelper.asMap(args);
         final boolean noExit = Boolean.parseBoolean(options.getOrDefault("noExit", "false").toString());
         final int parallelism = Integer.parseInt(options.getOrDefault("parallelism", "16").toString());
         final BasicThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("profiler-%d").build();
         final ExecutorService executor = Executors.newFixedThreadPool(parallelism, threadFactory);
+        final TestType testType = TestType.values()[(Integer.parseInt(options.getOrDefault("testType", "1").toString()) % TestType.values().length)];
 
         final String host = options.getOrDefault("host", "localhost").toString();
         final int minExpectedRps = Integer.parseInt(options.getOrDefault("minExpectedRps", "1000").toString());
@@ -155,7 +192,7 @@
         final int maxInProcessPerConnection = Integer.parseInt(options.getOrDefault("maxInProcessPerConnection", "64").toString());
         final int minInProcessPerConnection = Integer.parseInt(options.getOrDefault("minInProcessPerConnection", "16").toString());
         final int maxWaitForConnection = Integer.parseInt(options.getOrDefault("maxWaitForConnection", "3000").toString());
-        final int workerPoolSize = Integer.parseInt(options.getOrDefault("workerPoolSize", "2").toString());
+        final int workerPoolSize = Integer.parseInt(options.getOrDefault("workerPoolSize", Runtime.getRuntime().availableProcessors() * 2).toString());
         final int tooSlowThreshold = Integer.parseInt(options.getOrDefault("tooSlowThreshold", "125").toString());
         final String channelizer = options.getOrDefault("channelizer", Channelizer.WebSocketChannelizer.class.getName()).toString();
         final String serializer = options.getOrDefault("serializer", Serializers.GRAPHBINARY_V1.name()).toString();
@@ -177,6 +214,12 @@
                 .workerPoolSize(workerPoolSize).create();
 
         try {
+            if (TestType.LATENCY == testType) {
+                System.out.println("-----------------------LATENCY TEST SELECTED----------------------");
+            } else {
+                System.out.println("---------------------THROUGHPUT TEST SELECTED---------------------");
+            }
+
             if (exercise) {
                 System.out.println("--------------------------INITIALIZATION--------------------------");
                 final Client client = cluster.connect();
@@ -189,43 +232,72 @@
                 System.out.println("Modern graph loaded");
             }
 
-            final Object fileName = options.get("store");
-            final File f = null == fileName ? null : new File(fileName.toString());
-            if (f != null && f.length() == 0) {
-                try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
-                    writer.println("parallelism\tnioPoolSize\tminConnectionPoolSize\tmaxConnectionPoolSize\tminSimultaneousUsagePerConnection\tmaxSimultaneousUsagePerConnection\tminInProcessPerConnection\tmaxInProcessPerConnection\tworkerPoolSize\trequestPerSecond");
+            if (TestType.THROUGHPUT == testType) {
+                final Object fileName = options.get("store");
+                final File f = null == fileName ? null : new File(fileName.toString());
+                if (f != null && f.length() == 0) {
+                    try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
+                        writer.println("parallelism\tnioPoolSize\tminConnectionPoolSize\tmaxConnectionPoolSize\tminSimultaneousUsagePerConnection\tmaxSimultaneousUsagePerConnection\tminInProcessPerConnection\tmaxInProcessPerConnection\tworkerPoolSize\trequestPerSecond");
+                    }
                 }
-            }
 
-            // not much point to continuing with a line of tests if we can't get at least minExpectedRps.
-            final AtomicBoolean meetsRpsExpectation = new AtomicBoolean(true);
-            System.out.println("---------------------------WARMUP CYCLE---------------------------");
-            for (int ix = 0; ix < warmups && meetsRpsExpectation.get(); ix++) {
-                final long averageRequestsPerSecond = new ProfilingApplication("warmup-" + (ix + 1), cluster, 1000, executor, script, tooSlowThreshold, exercise).execute();
-                meetsRpsExpectation.set(averageRequestsPerSecond > minExpectedRps);
-                TimeUnit.SECONDS.sleep(1); // pause between executions
-            }
-
-            final AtomicBoolean exceededTimeout = new AtomicBoolean(false);
-            long totalRequestsPerSecond = 0;
-
-            // no need to execute this if we didn't pass the basic expectation in the warmups
-            if (exercise || meetsRpsExpectation.get()) {
-                final long start = System.nanoTime();
-                System.out.println("----------------------------TEST CYCLE----------------------------");
-                for (int ix = 0; ix < executions && !exceededTimeout.get(); ix++) {
-                    totalRequestsPerSecond += new ProfilingApplication("test-" + (ix + 1), cluster, requests, executor, script, tooSlowThreshold, exercise).execute();
-                    exceededTimeout.set((System.nanoTime() - start) > TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
+                // not much point to continuing with a line of tests if we can't get at least minExpectedRps.
+                final AtomicBoolean meetsRpsExpectation = new AtomicBoolean(true);
+                System.out.println("---------------------------WARMUP CYCLE---------------------------");
+                for (int ix = 0; ix < warmups && meetsRpsExpectation.get(); ix++) {
+                    final long averageRequestsPerSecond = new ProfilingApplication("warmup-" + (ix + 1), cluster, 1000, executor, script, tooSlowThreshold, exercise).executeThroughput();
+                    meetsRpsExpectation.set(averageRequestsPerSecond > minExpectedRps);
                     TimeUnit.SECONDS.sleep(1); // pause between executions
                 }
-            }
 
-            final int averageRequestPerSecond = !meetsRpsExpectation.get() || exceededTimeout.get() ? 0 : Math.round(totalRequestsPerSecond / executions);
-            System.out.println(String.format("avg req/sec: %s", averageRequestPerSecond));
-            if (f != null) {
-                try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
-                    writer.println(String.join("\t", String.valueOf(parallelism), String.valueOf(nioPoolSize), String.valueOf(minConnectionPoolSize), String.valueOf(maxConnectionPoolSize), String.valueOf(minSimultaneousUsagePerConnection), String.valueOf(maxSimultaneousUsagePerConnection), String.valueOf(minInProcessPerConnection), String.valueOf(maxInProcessPerConnection), String.valueOf(workerPoolSize), String.valueOf(averageRequestPerSecond)));
+                final AtomicBoolean exceededTimeout = new AtomicBoolean(false);
+                long totalRequestsPerSecond = 0;
+
+                // no need to execute this if we didn't pass the basic expectation in the warmups
+                if (exercise || meetsRpsExpectation.get()) {
+                    final long start = System.nanoTime();
+                    System.out.println("----------------------------TEST CYCLE----------------------------");
+                    for (int ix = 0; ix < executions && !exceededTimeout.get(); ix++) {
+                        totalRequestsPerSecond += new ProfilingApplication("test-" + (ix + 1), cluster, requests, executor, script, tooSlowThreshold, exercise).executeThroughput();
+                        exceededTimeout.set((System.nanoTime() - start) > TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
+                        TimeUnit.SECONDS.sleep(1); // pause between executions
+                    }
                 }
+
+                final int averageRequestPerSecond = !meetsRpsExpectation.get() || exceededTimeout.get() ? 0 : Math.round(totalRequestsPerSecond / executions);
+                System.out.println(String.format("avg req/sec: %s", averageRequestPerSecond));
+                if (f != null) {
+                    try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
+                        writer.println(String.join("\t", String.valueOf(parallelism), String.valueOf(nioPoolSize), String.valueOf(minConnectionPoolSize), String.valueOf(maxConnectionPoolSize), String.valueOf(minSimultaneousUsagePerConnection), String.valueOf(maxSimultaneousUsagePerConnection), String.valueOf(minInProcessPerConnection), String.valueOf(maxInProcessPerConnection), String.valueOf(workerPoolSize), String.valueOf(averageRequestPerSecond)));
+                    }
+                }
+            } else if (TestType.LATENCY == testType) {
+                final AtomicBoolean meetsTimeoutExpectation = new AtomicBoolean(true);
+                System.out.println("---------------------------WARMUP CYCLE---------------------------");
+                for (int ix = 0; ix < warmups && meetsTimeoutExpectation.get(); ix++) {
+                    final double latency = new ProfilingApplication("warmup-" + (ix + 1), cluster, 1000, executor, script, tooSlowThreshold, exercise).executeLatency();
+                    meetsTimeoutExpectation.set(latency < timeout);
+                    TimeUnit.SECONDS.sleep(1); // pause between executions
+                }
+
+                final AtomicBoolean exceededTimeout = new AtomicBoolean(false);
+                double totalTime = 0;
+
+                // no need to execute this if we didn't pass the basic expectation in the warmups
+                if (exercise || meetsTimeoutExpectation.get()) {
+                    final long start = System.nanoTime();
+                    System.out.println("----------------------------TEST CYCLE----------------------------");
+                    for (int ix = 0; ix < executions && !exceededTimeout.get(); ix++) {
+                        totalTime += new ProfilingApplication("test-" + (ix + 1), cluster, requests, executor, script, tooSlowThreshold, exercise).executeLatency();
+                        exceededTimeout.set((System.nanoTime() - start) > TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
+                        TimeUnit.SECONDS.sleep(1); // pause between executions
+                    }
+                }
+
+                final double averageLatency = !meetsTimeoutExpectation.get() || exceededTimeout.get() ? 0 : (totalTime / executions);
+                System.out.println(String.format("avg latency (sec/req): %s", averageLatency));
+            } else {
+                System.out.println("Encountered unknown testType. Please enter a valid value and try again.");
             }
 
             if (!noExit) System.exit(0);
diff --git a/gremlin-go/docker-compose.yml b/gremlin-go/docker-compose.yml
index a12e4f2..07c49a7 100644
--- a/gremlin-go/docker-compose.yml
+++ b/gremlin-go/docker-compose.yml
@@ -15,8 +15,6 @@
 #    specific language governing permissions and limitations
 #    under the License.
 
-version: "3.8"
-
 services:
 
   gremlin-server-test:
diff --git a/gremlin-go/driver/README.md b/gremlin-go/driver/README.md
index bbae31e..6524328 100644
--- a/gremlin-go/driver/README.md
+++ b/gremlin-go/driver/README.md
@@ -132,11 +132,11 @@
 There are different ways to launch the test suite and set the `GREMLIN_SERVER` environment variable depending on your Platform:
 - Run Maven commands, e.g. `mvn clean install` inside of `tinkerpop/gremlin-go`, or `mvn clean install -pl gremlin-go` inside of `tinkerpop` (platform-agnostic - recommended)
 - Run the `run.sh` script, which sets `GREMLIN_SERVER` by default. Run `./run.sh -h` for usage information (Unix/Linux - recommended).
-- Add `GREMLIN_SERVER=<server-image-version>` and `HOME=<user-home-directory>` to an `.env` file inside `gremlin-go` and run `docker-compose up --exit-code-from gremlin-go-integration-tests` (Platform-agnostic).
-- Run `GREMLIN_SERVER=<server-image-version> docker-compose up --exit-code-from gremlin-go-integration-tests` in Unix/Linux.
-- Run `$env:GREMLIN_SERVER="<server-image-version>";$env:HOME=$env:USERPROFILE;docker-compose up --exit-code-from gremlin-go-integration-tests` in Windows PowerShell.
+- Add `GREMLIN_SERVER=<server-image-version>` and `HOME=<user-home-directory>` to an `.env` file inside `gremlin-go` and run `docker compose up --exit-code-from gremlin-go-integration-tests` (Platform-agnostic).
+- Run `GREMLIN_SERVER=<server-image-version> docker compose up --exit-code-from gremlin-go-integration-tests` in Unix/Linux.
+- Run `$env:GREMLIN_SERVER="<server-image-version>";$env:HOME=$env:USERPROFILE;docker compose up --exit-code-from gremlin-go-integration-tests` in Windows PowerShell.
 
-You should see exit code 0 upon successful completion of the test suites. Run `docker-compose down` to remove the service containers (not needed if you executed Maven commands or `run.sh`), or `docker-compose down --rmi all` to remove the service containers while deleting all used images.
+You should see exit code 0 upon successful completion of the test suites. Run `docker compose down` to remove the service containers (not needed if you executed Maven commands or `run.sh`), or `docker compose down --rmi all` to remove the service containers while deleting all used images.
 
 [go]: https://go.dev/dl/
 [gomods]: https://go.dev/blog/using-go-modules
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 677a4dd..0bd7ba6 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -720,7 +720,8 @@
     "g_injectXa_null_bX_intersectXa_cX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Intersect(p["xx2"])}}, 
     "g_injectXa_null_bX_intersectXa_null_cX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Intersect(p["xx2"])}}, 
     "g_injectX3_threeX_intersectXfive_three_7X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Intersect(p["xx2"])}}, 
-    "g_injectX__feature___test__nullX_lTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("  feature", " one test", nil, "", " ").LTrim()}}, 
+    "g_injectX__feature___test__nullX_lTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("  feature", " one test", nil, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000").LTrim()}}, 
+    "g_injectX__feature___test__nullX_lTrimXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{"  feature  ", " one test ", nil, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"}).LTrim(gremlingo.Scope.Local)}}, 
     "g_injectX__feature__X_lTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("  feature  ").LTrim()}}, 
     "g_injectXListXa_bXX_lTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).LTrim()}}, 
     "g_injectXListX1_2XX_lTrimXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).LTrim(gremlingo.Scope.Local)}}, 
@@ -805,6 +806,7 @@
     "g_V_repeatXbothX_timesX5X_age_max": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Repeat(gremlingo.T__.Both()).Times(5).Values("age").Max()}}, 
     "g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Group().By("name").By(gremlingo.T__.BothE().Values("weight").Max())}}, 
     "g_VX1X_valuesXageX_maxXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Max(gremlingo.Scope.Local)}}, 
+    "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Max(gremlingo.Scope.Local)}}, 
     "g_V_age_mean": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Mean()}}, 
     "g_V_foo_mean": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Mean()}}, 
     "g_V_age_fold_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Fold().Mean(gremlingo.Scope.Local)}}, 
@@ -821,6 +823,7 @@
     "g_injectXnull_10_20_nullX_mean": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil, p["xx1"], p["xx2"], nil).Mean()}}, 
     "g_injectXlistXnull_10_20_nullXX_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Mean(gremlingo.Scope.Local)}}, 
     "g_VX1X_valuesXageX_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Mean(gremlingo.Scope.Local)}}, 
+    "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Mean(gremlingo.Scope.Local)}}, 
     "g_injectXnullX_mergeXinjectX1XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).Merge(gremlingo.T__.Inject(1))}}, 
     "g_V_valuesXnameX_mergeXV_foldX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Merge(gremlingo.T__.V().Fold())}}, 
     "g_V_fold_mergeXconstantXnullXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Fold().Merge(gremlingo.T__.Constant(nil))}}, 
@@ -966,6 +969,7 @@
     "g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Group().By("name").By(gremlingo.T__.BothE().Values("weight").Min())}}, 
     "g_V_foo_injectX9999999999X_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Inject(p["xx1"]).Min()}}, 
     "g_VX1X_valuesXageX_minXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Min(gremlingo.Scope.Local)}}, 
+    "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Min(gremlingo.Scope.Local)}}, 
     "g_V_name_order": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Order()}}, 
     "g_V_name_order_byXa1_b1X_byXb2_a2X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Order().By(p["c1"]).By(p["c2"])}}, 
     "g_V_order_byXname_ascX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By("name", gremlingo.Order.Asc).Values("name")}}, 
@@ -1046,7 +1050,8 @@
     "g_V_hasXageX_propertiesXage_nameX_value": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("age").Properties("age", "name").Value()}}, 
     "g_V_propertiesXname_age_nullX_value": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties("name", "age", nil).Value()}}, 
     "g_V_valuesXname_age_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name", "age", nil)}}, 
-    "g_injectX__feature___test__nullX_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("feature  ", "one test ", nil, "", " ").RTrim()}}, 
+    "g_injectX__feature___test__nullX_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("feature  ", "one test ", nil, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000").RTrim()}}, 
+    "g_injectX__feature___test__nullX_rTrimXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{"  feature  ", " one test ", nil, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"}).RTrim(gremlingo.Scope.Local)}}, 
     "g_injectX__feature__X_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("  feature  ").RTrim()}}, 
     "g_injectXListXa_bXX_rTrim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).RTrim()}}, 
     "g_injectXListX1_2XX_rTrimXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).RTrim(gremlingo.Scope.Local)}}, 
@@ -1183,6 +1188,7 @@
     "g_injectXnull_10_5_nullX_sum": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil, p["xx1"], p["xx2"], nil).Sum()}}, 
     "g_injectXlistXnull_10_5_nullXX_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Sum(gremlingo.Scope.Local)}}, 
     "g_VX1X_valuesXageX_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Sum(gremlingo.Scope.Local)}}, 
+    "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Sum(gremlingo.Scope.Local)}}, 
     "g_injectXfeature_test_nullX_toLower": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("FEATURE", "tESt", nil).ToLower()}}, 
     "g_injectXfeature_test_nullX_toLowerXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).ToLower(gremlingo.Scope.Local)}}, 
     "g_injectXListXa_bXX_toLower": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).ToLower()}}, 
@@ -1196,7 +1202,8 @@
     "g_V_valuesXnameX_toUpper": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").ToUpper()}}, 
     "g_V_valuesXnameX_toUpperXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").ToUpper(gremlingo.Scope.Local)}}, 
     "g_V_valuesXnameX_order_fold_toUpperXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Order().Fold().ToUpper(gremlingo.Scope.Local)}}, 
-    "g_injectX__feature___test__nullX_trim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("  feature  ", " one test ", nil, "", " ").Trim()}}, 
+    "g_injectX__feature___test__nullX_trim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("  feature  ", " one test ", nil, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000").Trim()}}, 
+    "g_injectX__feature___test__nullX_trimXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{"  feature  ", " one test ", nil, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"}).Trim(gremlingo.Scope.Local)}}, 
     "g_injectXListXa_bXX_trim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Trim()}}, 
     "g_injectXListX1_2XX_trimXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Trim(gremlingo.Scope.Local)}}, 
     "g_V_valuesXnameX_trim": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", " marko ").Property("age", 29).As("marko").AddV("person").Property("name", "  vadas  ").Property("age", 27).As("vadas").AddV("software").Property("name", "  lop").Property("lang", "java").As("lop").AddV("person").Property("name", "josh  ").Property("age", 32).As("josh").AddV("software").Property("name", "   ripple   ").Property("lang", "java").As("ripple").AddV("person").Property("name", "peter").Property("age", 35).As("peter").AddE("knows").From("marko").To("vadas").Property("weight", 0.5).AddE("knows").From("marko").To("josh").Property("weight", 1.0).AddE("created").From("marko").To("lop").Property("weight", 0.4).AddE("created").From("josh").To("ripple").Property("weight", 1.0).AddE("created").From("josh").To("lop").Property("weight", 0.4).AddE("created").From("peter").To("lop").Property("weight", 0.2)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Trim()}}, 
diff --git a/gremlin-go/driver/graphBinary.go b/gremlin-go/driver/graphBinary.go
index f9eb548..839a891 100644
--- a/gremlin-go/driver/graphBinary.go
+++ b/gremlin-go/driver/graphBinary.go
@@ -989,14 +989,14 @@
 		}
 		if k == nil {
 			mapData[nil] = v
+		} else if reflect.TypeOf(k).Comparable() {
+			mapData[k] = v
 		} else {
 			switch reflect.TypeOf(k).Kind() {
 			case reflect.Map:
 				mapData[&k] = v
-			case reflect.Slice:
-				mapData[fmt.Sprint(k)] = v
 			default:
-				mapData[k] = v
+				mapData[fmt.Sprint(k)] = v
 			}
 		}
 	}
@@ -1009,7 +1009,7 @@
 	for j := uint32(0); j < sz; j++ {
 		keyDataType := readDataType(data, i)
 		if keyDataType != stringType {
-			return nil, newError(err0703ReadMapNonStringKeyError)
+			return nil, newError(err0703ReadMapNonStringKeyError, keyDataType)
 		}
 
 		// Skip nullable, key must be present
diff --git a/gremlin-go/driver/graphBinary_test.go b/gremlin-go/driver/graphBinary_test.go
index 5bfcf68..d8c66d2 100644
--- a/gremlin-go/driver/graphBinary_test.go
+++ b/gremlin-go/driver/graphBinary_test.go
@@ -232,6 +232,76 @@
 			assert.Nil(t, err)
 			assert.Equal(t, fmt.Sprintf("%v", source), fmt.Sprintf("%v", res))
 		})
+		t.Run("read incomparable map: a map value as the key", func(t *testing.T) {
+			// prepare test data
+			var buf = &bytes.Buffer{}
+			typeSerializer := &graphBinaryTypeSerializer{}
+			// write the size of map
+			err := binary.Write(buf, binary.BigEndian, uint32(1))
+			if err != nil {
+				t.Fatalf("Failed to write data: %v", err)
+			}
+			// write a map value as the key
+			k1 := map[string]string{"key": "value"}
+			_, err = typeSerializer.write(reflect.ValueOf(k1).Interface(), buf)
+			if err != nil {
+				t.Fatalf("Failed to encode data: %v", err)
+			}
+			v1 := "value1"
+			_, err = typeSerializer.write(reflect.ValueOf(v1).Interface(), buf)
+			if err != nil {
+				t.Fatalf("Failed to encode data: %v", err)
+			}
+
+			data := buf.Bytes()
+			i := 0
+			result, err := readMap(&data, &i)
+			if err != nil {
+				t.Fatalf("readMap failed: %v", err)
+			}
+			mResult, ok := result.(map[interface{}]interface{})
+			if !ok {
+				t.Fatalf("readMap result not map[interface{}]interface{}")
+			}
+			for k, v := range mResult {
+				assert.Equal(t, reflect.Ptr, reflect.TypeOf(k).Kind())
+				assert.Equal(t, "value1", v)
+			}
+		})
+		t.Run("read incomparable map: a slice value as the key", func(t *testing.T) {
+			// prepare test data
+			var buf = &bytes.Buffer{}
+			typeSerializer := &graphBinaryTypeSerializer{}
+			// write the size of map
+			err := binary.Write(buf, binary.BigEndian, uint32(1))
+			if err != nil {
+				t.Fatalf("Failed to write data: %v", err)
+			}
+			// write a slice value as the key
+			k2 := []int{1, 2, 3}
+			_, err = typeSerializer.write(reflect.ValueOf(k2).Interface(), buf)
+			if err != nil {
+				t.Fatalf("Failed to encode data: %v", err)
+			}
+			v2 := "value2"
+			_, err = typeSerializer.write(reflect.ValueOf(v2).Interface(), buf)
+			if err != nil {
+				t.Fatalf("Failed to encode data: %v", err)
+			}
+
+			data := buf.Bytes()
+			i := 0
+			result, err := readMap(&data, &i)
+			if err != nil {
+				t.Fatalf("readMap failed: %v", err)
+			}
+			expected := map[interface{}]interface{}{
+				"[1 2 3]": "value2",
+			}
+			if !reflect.DeepEqual(result, expected) {
+				t.Errorf("Expected %v, but got %v", expected, result)
+			}
+		})
 		t.Run("read-write time", func(t *testing.T) {
 			pos := 0
 			var buffer bytes.Buffer
@@ -250,7 +320,7 @@
 			buff := []byte{0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01}
 			m, err := readMapUnqualified(&buff, &i)
 			assert.Nil(t, m)
-			assert.Equal(t, newError(err0703ReadMapNonStringKeyError), err)
+			assert.Equal(t, newError(err0703ReadMapNonStringKeyError, intType), err)
 		})
 	})
 }
diff --git a/gremlin-go/pom.xml b/gremlin-go/pom.xml
index aaf75d0..e7a2a64 100644
--- a/gremlin-go/pom.xml
+++ b/gremlin-go/pom.xml
@@ -113,8 +113,9 @@
                                         <!-- setting this env variable is needed to be cross-platform compatible -->
                                         <HOME>${user.home}</HOME>
                                     </environmentVariables>
-                                    <executable>docker-compose</executable>
+                                    <executable>docker</executable>
                                     <arguments>
+                                        <argument>compose</argument>
                                         <argument>up</argument>
                                         <argument>--build</argument>
                                         <argument>--exit-code-from</argument>
@@ -131,8 +132,9 @@
                                 <configuration>
                                     <skip>${skipTests}</skip>
                                     <!-- don't need to set env variables for container tear down -->
-                                    <executable>docker-compose</executable>
+                                    <executable>docker</executable>
                                     <arguments>
+                                        <argument>compose</argument>
                                         <argument>down</argument>
                                     </arguments>
                                 </configuration>
diff --git a/gremlin-go/run.sh b/gremlin-go/run.sh
index 5f62072..23a3763 100755
--- a/gremlin-go/run.sh
+++ b/gremlin-go/run.sh
@@ -48,8 +48,8 @@
 export ABS_PROJECT_HOME
 
 # Passes current gremlin server version into docker compose as environment variable
-docker-compose up --build --exit-code-from gremlin-go-integration-tests
+docker compose up --build --exit-code-from gremlin-go-integration-tests
 EXIT_CODE=$?
 # Removes all service containers
-docker-compose down
+docker compose down
 exit $EXIT_CODE
diff --git a/gremlin-javascript/pom.xml b/gremlin-javascript/pom.xml
index d2af8f0..21bcc40 100644
--- a/gremlin-javascript/pom.xml
+++ b/gremlin-javascript/pom.xml
@@ -256,8 +256,9 @@
                                         <!-- setting this env variable is needed to be cross-platform compatible -->
                                         <HOME>${user.home}</HOME>
                                     </environmentVariables>
-                                    <executable>docker-compose</executable>
+                                    <executable>docker</executable>
                                     <arguments>
+                                        <argument>compose</argument>
                                         <argument>up</argument>
                                         <argument>--build</argument>
                                         <argument>--exit-code-from</argument>
@@ -275,8 +276,9 @@
                                 <configuration>
                                     <skip>${skipTests}</skip>
                                     <!-- don't need to set env variables for container tear down -->
-                                    <executable>docker-compose</executable>
+                                    <executable>docker</executable>
                                     <arguments>
+                                        <argument>compose</argument>
                                         <argument>down</argument>
                                     </arguments>
                                     <workingDirectory>./src/main/javascript/gremlin-javascript</workingDirectory>
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/docker-compose.yml b/gremlin-javascript/src/main/javascript/gremlin-javascript/docker-compose.yml
index 65fc675..54b8392 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/docker-compose.yml
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/docker-compose.yml
@@ -15,8 +15,6 @@
 #    specific language governing permissions and limitations
 #    under the License.
 
-version: "3.8"
-
 services:
 
   gremlin-server-test-js:
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index fe3804f..c666394 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -740,7 +740,8 @@
     g_injectXa_null_bX_intersectXa_cX: [function({g, xx1, xx2}) { return g.inject(xx1).intersect(xx2) }], 
     g_injectXa_null_bX_intersectXa_null_cX: [function({g, xx1, xx2}) { return g.inject(xx1).intersect(xx2) }], 
     g_injectX3_threeX_intersectXfive_three_7X: [function({g, xx1, xx2}) { return g.inject(xx1).intersect(xx2) }], 
-    g_injectX__feature___test__nullX_lTrim: [function({g}) { return g.inject("  feature"," one test",null,""," ").lTrim() }], 
+    g_injectX__feature___test__nullX_lTrim: [function({g}) { return g.inject("  feature"," one test",null,""," ","\u3000abc","abc\u3000","\u3000abc\u3000","\u3000\u3000").lTrim() }], 
+    g_injectX__feature___test__nullX_lTrimXlocalX: [function({g}) { return g.inject(["  feature  ", " one test ", null, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"]).lTrim(Scope.local) }], 
     g_injectX__feature__X_lTrim: [function({g}) { return g.inject("  feature  ").lTrim() }], 
     g_injectXListXa_bXX_lTrim: [function({g, xx1}) { return g.inject(xx1).lTrim() }], 
     g_injectXListX1_2XX_lTrimXlocalX: [function({g, xx1}) { return g.inject(xx1).lTrim(Scope.local) }], 
@@ -825,6 +826,7 @@
     g_V_repeatXbothX_timesX5X_age_max: [function({g}) { return g.V().repeat(__.both()).times(5).values("age").max() }], 
     g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX: [function({g}) { return g.V().hasLabel("software").group().by("name").by(__.bothE().values("weight").max()) }], 
     g_VX1X_valuesXageX_maxXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").max(Scope.local) }], 
+    g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).max(Scope.local) }], 
     g_V_age_mean: [function({g}) { return g.V().values("age").mean() }], 
     g_V_foo_mean: [function({g}) { return g.V().values("foo").mean() }], 
     g_V_age_fold_meanXlocalX: [function({g}) { return g.V().values("age").fold().mean(Scope.local) }], 
@@ -841,6 +843,7 @@
     g_injectXnull_10_20_nullX_mean: [function({g, xx1, xx2}) { return g.inject(null,xx1,xx2,null).mean() }], 
     g_injectXlistXnull_10_20_nullXX_meanXlocalX: [function({g, xx1}) { return g.inject(xx1).mean(Scope.local) }], 
     g_VX1X_valuesXageX_meanXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").mean(Scope.local) }], 
+    g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).mean(Scope.local) }], 
     g_injectXnullX_mergeXinjectX1XX: [function({g}) { return g.inject(null).merge(__.inject(1)) }], 
     g_V_valuesXnameX_mergeXV_foldX: [function({g}) { return g.V().values("name").merge(__.V().fold()) }], 
     g_V_fold_mergeXconstantXnullXX: [function({g}) { return g.V().fold().merge(__.constant(null)) }], 
@@ -986,6 +989,7 @@
     g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX: [function({g}) { return g.V().hasLabel("software").group().by("name").by(__.bothE().values("weight").min()) }], 
     g_V_foo_injectX9999999999X_min: [function({g, xx1}) { return g.V().values("foo").inject(xx1).min() }], 
     g_VX1X_valuesXageX_minXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").min(Scope.local) }], 
+    g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).min(Scope.local) }], 
     g_V_name_order: [function({g}) { return g.V().values("name").order() }], 
     g_V_name_order_byXa1_b1X_byXb2_a2X: [function({g, c1, c2}) { return g.V().values("name").order().by(c1).by(c2) }], 
     g_V_order_byXname_ascX_name: [function({g}) { return g.V().order().by("name",Order.asc).values("name") }], 
@@ -1066,7 +1070,8 @@
     g_V_hasXageX_propertiesXage_nameX_value: [function({g}) { return g.V().has("age").properties("age","name").value() }], 
     g_V_propertiesXname_age_nullX_value: [function({g}) { return g.V().properties("name","age",null).value() }], 
     g_V_valuesXname_age_nullX: [function({g}) { return g.V().values("name","age",null) }], 
-    g_injectX__feature___test__nullX_rTrim: [function({g}) { return g.inject("feature  ","one test ",null,""," ").rTrim() }], 
+    g_injectX__feature___test__nullX_rTrim: [function({g}) { return g.inject("feature  ","one test ",null,""," ","\u3000abc","abc\u3000","\u3000abc\u3000","\u3000\u3000").rTrim() }], 
+    g_injectX__feature___test__nullX_rTrimXlocalX: [function({g}) { return g.inject(["  feature  ", " one test ", null, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"]).rTrim(Scope.local) }], 
     g_injectX__feature__X_rTrim: [function({g}) { return g.inject("  feature  ").rTrim() }], 
     g_injectXListXa_bXX_rTrim: [function({g, xx1}) { return g.inject(xx1).rTrim() }], 
     g_injectXListX1_2XX_rTrimXlocalX: [function({g, xx1}) { return g.inject(xx1).rTrim(Scope.local) }], 
@@ -1203,6 +1208,7 @@
     g_injectXnull_10_5_nullX_sum: [function({g, xx1, xx2}) { return g.inject(null,xx1,xx2,null).sum() }], 
     g_injectXlistXnull_10_5_nullXX_sumXlocalX: [function({g, xx1}) { return g.inject(xx1).sum(Scope.local) }], 
     g_VX1X_valuesXageX_sumXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").sum(Scope.local) }], 
+    g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).sum(Scope.local) }], 
     g_injectXfeature_test_nullX_toLower: [function({g}) { return g.inject("FEATURE","tESt",null).toLower() }], 
     g_injectXfeature_test_nullX_toLowerXlocalX: [function({g, xx1}) { return g.inject(xx1).toLower(Scope.local) }], 
     g_injectXListXa_bXX_toLower: [function({g, xx1}) { return g.inject(xx1).toLower() }], 
@@ -1216,7 +1222,8 @@
     g_V_valuesXnameX_toUpper: [function({g}) { return g.V().values("name").toUpper() }], 
     g_V_valuesXnameX_toUpperXlocalX: [function({g}) { return g.V().values("name").toUpper(Scope.local) }], 
     g_V_valuesXnameX_order_fold_toUpperXlocalX: [function({g}) { return g.V().values("name").order().fold().toUpper(Scope.local) }], 
-    g_injectX__feature___test__nullX_trim: [function({g}) { return g.inject("  feature  "," one test ",null,""," ").trim() }], 
+    g_injectX__feature___test__nullX_trim: [function({g}) { return g.inject("  feature  "," one test ",null,""," ","\u3000abc","abc\u3000","\u3000abc\u3000","\u3000\u3000").trim() }], 
+    g_injectX__feature___test__nullX_trimXlocalX: [function({g}) { return g.inject(["  feature  ", " one test ", null, "", " ", "\u3000abc", "abc\u3000", "\u3000abc\u3000", "\u3000\u3000"]).trim(Scope.local) }], 
     g_injectXListXa_bXX_trim: [function({g, xx1}) { return g.inject(xx1).trim() }], 
     g_injectXListX1_2XX_trimXlocalX: [function({g, xx1}) { return g.inject(xx1).trim(Scope.local) }], 
     g_V_valuesXnameX_trim: [function({g}) { return g.addV("person").property("name"," marko ").property("age",29).as("marko").addV("person").property("name","  vadas  ").property("age",27).as("vadas").addV("software").property("name","  lop").property("lang","java").as("lop").addV("person").property("name","josh  ").property("age",32).as("josh").addV("software").property("name","   ripple   ").property("lang","java").as("ripple").addV("person").property("name","peter").property("age",35).as("peter").addE("knows").from_("marko").to("vadas").property("weight",0.5).addE("knows").from_("marko").to("josh").property("weight",1.0).addE("created").from_("marko").to("lop").property("weight",0.4).addE("created").from_("josh").to("ripple").property("weight",1.0).addE("created").from_("josh").to("lop").property("weight",0.4).addE("created").from_("peter").to("lop").property("weight",0.2) }, function({g}) { return g.V().values("name").trim() }], 
diff --git a/gremlin-python/docker-compose.yml b/gremlin-python/docker-compose.yml
index 5f9ef5d..b03ec9c 100644
--- a/gremlin-python/docker-compose.yml
+++ b/gremlin-python/docker-compose.yml
@@ -15,8 +15,6 @@
 #    specific language governing permissions and limitations
 #    under the License.
 
-version: "3.8"
-
 services:
 
   gremlin-server-test-python:
@@ -72,7 +70,7 @@
       bash -c "apt-get update && apt-get -y install libkrb5-dev krb5-user
       && echo 'password' | kinit stephen
       && klist
-      && pip install wheel radish-bdd PyHamcrest aenum isodate kerberos six
+      && pip install wheel radish-bdd PyHamcrest aenum isodate kerberos
       && python3 ./setup.py build
       && python3 ./setup.py test
       && python3 ./setup.py install
diff --git a/gremlin-python/pom.xml b/gremlin-python/pom.xml
index 3dc2296..5299cf5 100644
--- a/gremlin-python/pom.xml
+++ b/gremlin-python/pom.xml
@@ -135,16 +135,16 @@
                                 </goals>
                                 <configuration>
                                     <target>
-                                        <exec executable="docker-compose" failonerror="true">
+                                        <exec executable="docker" failonerror="true">
                                             <env key="PACKAGE_DIR" value="${project.build.directory}/python-packaged"/>
                                             <env key="VERSION" value="${project.version}"/>
                                             <env key="PYTHONPATH" value=""/>
-                                            <arg line="up --build --abort-on-container-exit gremlin-python-package"/>
+                                            <arg line="compose up --build --abort-on-container-exit gremlin-python-package"/>
                                         </exec>
-                                        <exec executable="docker-compose" failonerror="true">
+                                        <exec executable="docker" failonerror="true">
                                             <env key="PYTHONPATH" value=""/>
                                             <env key="PACKAGE_DIR" value="${project.build.directory}/python-packaged"/>
-                                            <arg line="down"/>
+                                            <arg line="compose down"/>
                                         </exec>
                                         <exec executable="docker" failonerror="true">
                                             <env key="PYTHONPATH" value=""/>
@@ -156,7 +156,7 @@
                             </execution>
 
                             <!--
-                            use docker-compose to run unit tests, radish, and integration tests.
+                            use docker compose to run unit tests, radish, and integration tests.
                             -->
                             <execution>
                                 <id>python-tests</id>
@@ -167,18 +167,18 @@
                                 <configuration>
                                     <skip>${skipTests}</skip>
                                     <target>
-                                        <exec executable="docker-compose" failonerror="true">
+                                        <exec executable="docker" failonerror="true">
                                             <env key="VERSION" value="${project.version}"/>
                                             <env key="PYTHONPATH" value=""/>
                                             <env key="GREMLIN_SERVER" value="${project.version}"/>
                                             <env key="ABS_PROJECT_HOME" value="${project.basedir}/../"/>
                                             <env key="BUILD_DIR" value="${project.build.directory}/python3"/>
-                                            <arg line="up --build --abort-on-container-exit gremlin-server-test-python gremlin-python-integration-tests"/>
+                                            <arg line="compose up --build --abort-on-container-exit gremlin-server-test-python gremlin-python-integration-tests"/>
                                         </exec>
-                                        <exec executable="docker-compose" failonerror="true">
+                                        <exec executable="docker" failonerror="true">
                                             <env key="PYTHONPATH" value=""/>
                                             <env key="BUILD_DIR" value="${project.build.directory}/python3"/>
-                                            <arg line="down"/>
+                                            <arg line="compose down"/>
                                         </exec>
                                         <exec executable="docker" failonerror="true">
                                             <env key="PYTHONPATH" value=""/>
diff --git a/gremlin-python/src/main/python/examples/requirements.txt b/gremlin-python/src/main/python/examples/requirements.txt
index fb17e70..d4399ec 100644
--- a/gremlin-python/src/main/python/examples/requirements.txt
+++ b/gremlin-python/src/main/python/examples/requirements.txt
@@ -25,5 +25,4 @@
 idna==3.6
 isodate==0.6.1
 multidict==6.0.5
-six==1.16.0
 yarl==1.9.4
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 70c1751..8696202 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -722,7 +722,8 @@
     'g_injectXa_null_bX_intersectXa_cX': [(lambda g, xx1=None,xx2=None:g.inject(xx1).intersect(xx2))], 
     'g_injectXa_null_bX_intersectXa_null_cX': [(lambda g, xx1=None,xx2=None:g.inject(xx1).intersect(xx2))], 
     'g_injectX3_threeX_intersectXfive_three_7X': [(lambda g, xx1=None,xx2=None:g.inject(xx1).intersect(xx2))], 
-    'g_injectX__feature___test__nullX_lTrim': [(lambda g:g.inject('  feature',' one test',None,'',' ').lTrim())], 
+    'g_injectX__feature___test__nullX_lTrim': [(lambda g:g.inject('  feature',' one test',None,'',' ',' abc','abc ',' abc ','  ').lTrim())], 
+    'g_injectX__feature___test__nullX_lTrimXlocalX': [(lambda g:g.inject(['  feature  ',' one test ',None,'',' ',' abc','abc ',' abc ','  ']).lTrim(Scope.local))], 
     'g_injectX__feature__X_lTrim': [(lambda g:g.inject('  feature  ').lTrim())], 
     'g_injectXListXa_bXX_lTrim': [(lambda g, xx1=None:g.inject(xx1).lTrim())], 
     'g_injectXListX1_2XX_lTrimXlocalX': [(lambda g, xx1=None:g.inject(xx1).lTrim(Scope.local))], 
@@ -807,6 +808,7 @@
     'g_V_repeatXbothX_timesX5X_age_max': [(lambda g:g.V().repeat(__.both()).times(5).age.max_())], 
     'g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX': [(lambda g:g.V().hasLabel('software').group().by('name').by(__.bothE().weight.max_()))], 
     'g_VX1X_valuesXageX_maxXlocalX': [(lambda g, vid1=None:g.V(vid1).age.max_(Scope.local))], 
+    'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).max_(Scope.local))], 
     'g_V_age_mean': [(lambda g:g.V().age.mean())], 
     'g_V_foo_mean': [(lambda g:g.V().foo.mean())], 
     'g_V_age_fold_meanXlocalX': [(lambda g:g.V().age.fold().mean(Scope.local))], 
@@ -823,6 +825,7 @@
     'g_injectXnull_10_20_nullX_mean': [(lambda g, xx1=None,xx2=None:g.inject(None,xx1,xx2,None).mean())], 
     'g_injectXlistXnull_10_20_nullXX_meanXlocalX': [(lambda g, xx1=None:g.inject(xx1).mean(Scope.local))], 
     'g_VX1X_valuesXageX_meanXlocalX': [(lambda g, vid1=None:g.V(vid1).age.mean(Scope.local))], 
+    'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).mean(Scope.local))], 
     'g_injectXnullX_mergeXinjectX1XX': [(lambda g:g.inject(None).merge(__.inject(1)))], 
     'g_V_valuesXnameX_mergeXV_foldX': [(lambda g:g.V().name.merge(__.V().fold()))], 
     'g_V_fold_mergeXconstantXnullXX': [(lambda g:g.V().fold().merge(__.constant(None)))], 
@@ -968,6 +971,7 @@
     'g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX': [(lambda g:g.V().hasLabel('software').group().by('name').by(__.bothE().weight.min_()))], 
     'g_V_foo_injectX9999999999X_min': [(lambda g, xx1=None:g.V().foo.inject(xx1).min_())], 
     'g_VX1X_valuesXageX_minXlocalX': [(lambda g, vid1=None:g.V(vid1).age.min_(Scope.local))], 
+    'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).min_(Scope.local))], 
     'g_V_name_order': [(lambda g:g.V().name.order())], 
     'g_V_name_order_byXa1_b1X_byXb2_a2X': [(lambda g, c1=None,c2=None:g.V().name.order().by(c1).by(c2))], 
     'g_V_order_byXname_ascX_name': [(lambda g:g.V().order().by('name',Order.asc).name)], 
@@ -1048,7 +1052,8 @@
     'g_V_hasXageX_propertiesXage_nameX_value': [(lambda g:g.V().has('age').properties('age','name').value())], 
     'g_V_propertiesXname_age_nullX_value': [(lambda g:g.V().properties('name','age',None).value())], 
     'g_V_valuesXname_age_nullX': [(lambda g:g.V().values('name','age',None))], 
-    'g_injectX__feature___test__nullX_rTrim': [(lambda g:g.inject('feature  ','one test ',None,'',' ').rTrim())], 
+    'g_injectX__feature___test__nullX_rTrim': [(lambda g:g.inject('feature  ','one test ',None,'',' ',' abc','abc ',' abc ','  ').rTrim())], 
+    'g_injectX__feature___test__nullX_rTrimXlocalX': [(lambda g:g.inject(['  feature  ',' one test ',None,'',' ',' abc','abc ',' abc ','  ']).rTrim(Scope.local))], 
     'g_injectX__feature__X_rTrim': [(lambda g:g.inject('  feature  ').rTrim())], 
     'g_injectXListXa_bXX_rTrim': [(lambda g, xx1=None:g.inject(xx1).rTrim())], 
     'g_injectXListX1_2XX_rTrimXlocalX': [(lambda g, xx1=None:g.inject(xx1).rTrim(Scope.local))], 
@@ -1185,6 +1190,7 @@
     'g_injectXnull_10_5_nullX_sum': [(lambda g, xx1=None,xx2=None:g.inject(None,xx1,xx2,None).sum_())], 
     'g_injectXlistXnull_10_5_nullXX_sumXlocalX': [(lambda g, xx1=None:g.inject(xx1).sum_(Scope.local))], 
     'g_VX1X_valuesXageX_sumXlocalX': [(lambda g, vid1=None:g.V(vid1).age.sum_(Scope.local))], 
+    'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).sum_(Scope.local))], 
     'g_injectXfeature_test_nullX_toLower': [(lambda g:g.inject('FEATURE','tESt',None).to_lower())], 
     'g_injectXfeature_test_nullX_toLowerXlocalX': [(lambda g, xx1=None:g.inject(xx1).to_lower(Scope.local))], 
     'g_injectXListXa_bXX_toLower': [(lambda g, xx1=None:g.inject(xx1).to_lower())], 
@@ -1198,7 +1204,8 @@
     'g_V_valuesXnameX_toUpper': [(lambda g:g.V().name.to_upper())], 
     'g_V_valuesXnameX_toUpperXlocalX': [(lambda g:g.V().name.to_upper(Scope.local))], 
     'g_V_valuesXnameX_order_fold_toUpperXlocalX': [(lambda g:g.V().name.order().fold().to_upper(Scope.local))], 
-    'g_injectX__feature___test__nullX_trim': [(lambda g:g.inject('  feature  ',' one test ',None,'',' ').trim())], 
+    'g_injectX__feature___test__nullX_trim': [(lambda g:g.inject('  feature  ',' one test ',None,'',' ',' abc','abc ',' abc ','  ').trim())], 
+    'g_injectX__feature___test__nullX_trimXlocalX': [(lambda g:g.inject(['  feature  ',' one test ',None,'',' ',' abc','abc ',' abc ','  ']).trim(Scope.local))], 
     'g_injectXListXa_bXX_trim': [(lambda g, xx1=None:g.inject(xx1).trim())], 
     'g_injectXListX1_2XX_trimXlocalX': [(lambda g, xx1=None:g.inject(xx1).trim(Scope.local))], 
     'g_V_valuesXnameX_trim': [(lambda g:g.addV('person').property('name',' marko ').property('age',29).as_('marko').addV('person').property('name','  vadas  ').property('age',27).as_('vadas').addV('software').property('name','  lop').property('lang','java').as_('lop').addV('person').property('name','josh  ').property('age',32).as_('josh').addV('software').property('name','   ripple   ').property('lang','java').as_('ripple').addV('person').property('name','peter').property('age',35).as_('peter').addE('knows').from_('marko').to('vadas').property('weight',float(0.5)).addE('knows').from_('marko').to('josh').property('weight',float(1.0)).addE('created').from_('marko').to('lop').property('weight',float(0.4)).addE('created').from_('josh').to('ripple').property('weight',float(1.0)).addE('created').from_('josh').to('lop').property('weight',float(0.4)).addE('created').from_('peter').to('lop').property('weight',float(0.2))), (lambda g:g.V().name.trim())], 
diff --git a/gremlin-python/src/main/python/setup.cfg b/gremlin-python/src/main/python/setup.cfg
index a4a55e0..d63f2e1 100644
--- a/gremlin-python/src/main/python/setup.cfg
+++ b/gremlin-python/src/main/python/setup.cfg
@@ -14,9 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-[bdist_wheel]
-universal=1
-
 [aliases]
 test=pytest
 
diff --git a/gremlin-python/src/main/python/setup.py b/gremlin-python/src/main/python/setup.py
index 2ca7e9a..88d4d8c 100644
--- a/gremlin-python/src/main/python/setup.py
+++ b/gremlin-python/src/main/python/setup.py
@@ -48,13 +48,9 @@
     'nest_asyncio',
     'aiohttp>=3.8.0,<4.0.0',
     'aenum>=1.4.5,<4.0.0',
-    'six>=1.10.0,<2.0.0',
     'isodate>=0.6.0,<1.0.0'
 ]
 
-if sys.version_info < (3, 5):
-    install_requires += ['pyparsing>=2.4.7,<3.0.0']
-
 setup(
     name='gremlinpython',
     version=version,
@@ -62,7 +58,7 @@
               'gremlin_python.driver.aiohttp', 'gremlin_python.process',
               'gremlin_python.structure', 'gremlin_python.structure.io'],
     license='Apache 2',
-    url='http://tinkerpop.apache.org',
+    url='https://tinkerpop.apache.org',
     description='Gremlin-Python for Apache TinkerPop',
     long_description=codecs.open("README.rst", "r", "UTF-8").read(),
     long_description_content_type='text/x-rst',
@@ -74,7 +70,6 @@
     ],
     tests_require=[
         'pytest>=4.6.4,<7.2.0',
-        'mock>=3.0.5,<5.0.0',
         'radish-bdd==0.13.4',
         'PyHamcrest>=1.9.0,<3.0.0',
         'PyYAML>=5.3'
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py
index f89c84c..4860350 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py
@@ -26,7 +26,7 @@
 import math
 from decimal import *
 
-from mock import Mock
+from unittest.mock import Mock
 
 from gremlin_python.statics import *
 from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Graph, Path
@@ -37,7 +37,7 @@
 from gremlin_python.process.graph_traversal import __
 
 
-class TestGraphSONReader(object):
+class TestGraphSONReader:
     graphson_reader = GraphSONReader()
 
     def test_number_input(self):
@@ -216,7 +216,7 @@
     def test_custom_mapping(self):
 
         # extended mapping
-        class X(object):
+        class X:
             pass
 
         type_string = "test:Xtype"
@@ -298,7 +298,7 @@
         assert c is None
 
 
-class TestGraphSONWriter(object):
+class TestGraphSONWriter:
     graphson_writer = GraphSONWriter()
     graphson_reader = GraphSONReader()
 
@@ -418,7 +418,7 @@
 
     def test_custom_mapping(self):
         # extended mapping
-        class X(object):
+        class X:
             pass
 
         serdes = Mock()
@@ -489,7 +489,7 @@
         assert expected == output
 
 
-class TestFunctionalGraphSONIO(object):
+class TestFunctionalGraphSONIO:
     """Functional IO tests"""
 
     def test_timestamp(self, remote_connection_graphsonV2):
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV3d0.py b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV3d0.py
index 6b852fb..a6a65a8 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV3d0.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV3d0.py
@@ -26,7 +26,7 @@
 import math
 from decimal import *
 
-from mock import Mock
+from unittest.mock import Mock
 
 from gremlin_python.statics import *
 from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path
@@ -37,7 +37,7 @@
 from gremlin_python.process.graph_traversal import __
 
 
-class TestGraphSONReader(object):
+class TestGraphSONReader:
     graphson_reader = GraphSONReader()
 
     def test_collections(self):
@@ -57,7 +57,7 @@
                                                      "3"]}))
         # return a set as normal
         assert isinstance(x, set)
-        assert x == set([1, 2, "3"])
+        assert x == {1, 2, "3"}
 
         x = self.graphson_reader.read_object(
             json.dumps({"@type": "g:Set", "@value": [{"@type": "g:Int32", "@value": 1},
@@ -261,7 +261,7 @@
     def test_custom_mapping(self):
 
         # extended mapping
-        class X(object):
+        class X:
             pass
 
         type_string = "test:Xtype"
@@ -344,7 +344,7 @@
         assert c is None
 
 
-class TestGraphSONWriter(object):
+class TestGraphSONWriter:
     graphson_writer = GraphSONWriter()
     graphson_reader = GraphSONReader()
 
@@ -356,7 +356,7 @@
         assert {"@type": "g:Set", "@value": [{"@type": "g:Int32", "@value": 1},
                                              {"@type": "g:Int32", "@value": 2},
                                              {"@type": "g:Int32", "@value": 3}]} == json.loads(
-            self.graphson_writer.write_object(set([1, 2, 3, 3])))
+            self.graphson_writer.write_object({1, 2, 3, 3}))
         assert {"@type": "g:Map",
                 "@value": ['a', {"@type": "g:Int32", "@value": 1}]} == json.loads(
             self.graphson_writer.write_object({'a': 1}))
@@ -484,7 +484,7 @@
 
     def test_custom_mapping(self):
         # extended mapping
-        class X(object):
+        class X:
             pass
 
         serdes = Mock()
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WebSocketAuthorizationHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WebSocketAuthorizationHandler.java
index 4b8e6f9..1220bce 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WebSocketAuthorizationHandler.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WebSocketAuthorizationHandler.java
@@ -68,11 +68,9 @@
                         final Bytecode bytecode = (Bytecode) requestMessage.getArgs().get(Tokens.ARGS_GREMLIN);
                         final Map<String, String> aliases = (Map<String, String>) requestMessage.getArgs().get(Tokens.ARGS_ALIASES);
                         final Bytecode restrictedBytecode = authorizer.authorize(user, bytecode, aliases);
-                        final RequestMessage restrictedMsg = RequestMessage.build(Tokens.OPS_BYTECODE).
-                                overrideRequestId(requestMessage.getRequestId()).
-                                processor("traversal").
-                                addArg(Tokens.ARGS_GREMLIN, restrictedBytecode).
-                                addArg(Tokens.ARGS_ALIASES, aliases).create();
+                        final RequestMessage restrictedMsg = RequestMessage.from(requestMessage)
+                                .addArg(Tokens.ARGS_GREMLIN, restrictedBytecode)
+                                .addArg(Tokens.ARGS_ALIASES, aliases).create();
                         ctx.fireChannelRead(restrictedMsg);
                         break;
                     case Tokens.OPS_EVAL:
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthzIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthzIntegrateTest.java
index 871ce4f..f461380 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthzIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthzIntegrateTest.java
@@ -31,7 +31,7 @@
 import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection;
 import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.AbstractWarningVerificationStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
 import org.apache.tinkerpop.gremlin.server.auth.AllowAllAuthenticator;
 import org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator;
 import org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer;
@@ -42,13 +42,16 @@
 import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.time.Instant;
 import java.util.Base64;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.concurrent.CompletionException;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
@@ -62,6 +65,7 @@
  * @author Marc de Lignie
  */
 public class GremlinServerAuthzIntegrateTest extends AbstractGremlinServerIntegrationTest {
+    private static final Long DEFAULT_EVALUATION_TIMEOUT = 2000L;
     private static LogCaptor logCaptor;
 
     private final ObjectMapper mapper = new ObjectMapper();
@@ -107,6 +111,7 @@
         settings.authentication = authSettings;
         settings.authorization = authzSettings;
         settings.enableAuditLog = true;
+        settings.evaluationTimeout = DEFAULT_EVALUATION_TIMEOUT;
 
         final String nameOfTest = name.getMethodName();
         switch (nameOfTest) {
@@ -387,4 +392,23 @@
             assertEquals(6, node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).intValue());
         }
     }
+
+    @Test
+    public void shouldRespectTimeoutWithAuth() {
+        final Cluster cluster = TestClientFactory.build().credentials("stephen", "password").create();
+        final GraphTraversalSource g = AnonymousTraversalSource.traversal().withRemote(
+                DriverRemoteConnection.using(cluster, "gmodern"));
+        final Instant instant = Instant.now();
+        try {
+            g.with("evaluationTimeout", DEFAULT_EVALUATION_TIMEOUT / 20).
+                    V().
+                    repeat(__.both()).
+                    until(__.count().is(0)).
+                    toList();
+        } catch (final CompletionException e) {
+            Assert.assertTrue(Instant.now().toEpochMilli() - instant.toEpochMilli() < DEFAULT_EVALUATION_TIMEOUT / 2);
+        } finally {
+            cluster.close();
+        }
+    }
 }
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/LTrim.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/LTrim.feature
index 4ca4915..67b664c 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/LTrim.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/LTrim.feature
@@ -19,11 +19,12 @@
 Feature: Step - lTrim()
 
   @GraphComputerVerificationInjectionNotSupported
+  # This verifies both ASCII control space and ideographic space character \u3000 are property trimmed.
   Scenario: g_injectX__feature___test__nullX_lTrim
     Given the empty graph
     And the traversal of
       """
-      g.inject("  feature", " one test", null, "", " ").lTrim()
+      g.inject("  feature", " one test", null, "", " ", " abc", "abc ", " abc ", "  ").lTrim()
       """
     When iterated to list
     Then the result should be unordered
@@ -33,6 +34,23 @@
       | null |
       | str[] |
       | str[] |
+      | str[abc] |
+      | str[abc ] |
+      | str[abc ] |
+      | str[] |
+
+  @GraphComputerVerificationInjectionNotSupported
+  # This verifies both ASCII control space and ideographic space character \u3000 are property trimmed.
+  Scenario: g_injectX__feature___test__nullX_lTrimXlocalX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(["  feature  ", " one test ", null, "", " ", " abc", "abc ", " abc ", "  "]).lTrim(Scope.local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[str[feature  ],str[one test ],null,str[],str[],str[abc],str[abc ],str[abc ],str[]] |
 
   @GraphComputerVerificationInjectionNotSupported
   Scenario: g_injectX__feature__X_lTrim
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature
index 1603370..75caa6a 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature
@@ -201,3 +201,18 @@
     Then the result should be unordered
       | result |
       | d[29].i |
+
+  # It verifies if empty lists are filtered out as expected
+  Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().local(union(values("age"), outE().values("weight")).fold()).max(local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | d[29].d |
+      | d[27].i |
+      | d[32].d |
+      | d[35].d |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature
index 75fc132..fed1a0c 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature
@@ -202,4 +202,19 @@
     When iterated to list
     Then the result should be unordered
       | result |
-      | d[29.0].d |
\ No newline at end of file
+      | d[29.0].d |
+
+  # It verifies if empty lists are filtered out as expected
+  Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().local(union(values("age"), outE().values("weight")).fold()).mean(local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | d[7.725].d |
+      | d[27.0].d |
+      | d[11.133333333333333].d |
+      | d[17.6].d |
\ No newline at end of file
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature
index 0b8c4af..cff82d8 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature
@@ -214,3 +214,18 @@
     Then the result should be unordered
       | result |
       | d[29].i |
+
+  # It verifies if empty lists are filtered out as expected
+  Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().local(union(values("age"), outE().values("weight")).fold()).min(local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | d[0.4].d |
+      | d[27].i |
+      | d[0.4].d |
+      | d[0.2].d |
\ No newline at end of file
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/RTrim.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/RTrim.feature
index 23df32f..4b171b8 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/RTrim.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/RTrim.feature
@@ -19,11 +19,12 @@
 Feature: Step - rTrim()
 
   @GraphComputerVerificationInjectionNotSupported
+  # This verifies both ASCII control space and ideographic space character \u3000 are property trimmed.
   Scenario: g_injectX__feature___test__nullX_rTrim
     Given the empty graph
     And the traversal of
       """
-      g.inject("feature  ", "one test ", null, "", " ").rTrim()
+      g.inject("feature  ", "one test ", null, "", " ", " abc", "abc ", " abc ", "  ").rTrim()
       """
     When iterated to list
     Then the result should be unordered
@@ -33,6 +34,23 @@
       | null |
       | str[] |
       | str[] |
+      | str[ abc] |
+      | str[abc] |
+      | str[ abc] |
+      | str[] |
+
+  @GraphComputerVerificationInjectionNotSupported
+  # This verifies both ASCII control space and ideographic space character \u3000 are property trimmed.
+  Scenario: g_injectX__feature___test__nullX_rTrimXlocalX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(["  feature  ", " one test ", null, "", " ", " abc", "abc ", " abc ", "  "]).rTrim(Scope.local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[str[  feature],str[ one test],null,str[],str[],str[ abc],str[abc],str[ abc],str[]] |
 
   @GraphComputerVerificationInjectionNotSupported
   Scenario: g_injectX__feature__X_rTrim
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature
index e0e7345..c5a475f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature
@@ -201,4 +201,19 @@
     When iterated to list
     Then the result should be unordered
       | result |
-      | d[29].i |
\ No newline at end of file
+      | d[29].i |
+
+      # It verifies if empty lists are filtered out as expected
+  Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().local(union(values("age"), outE().values("weight")).fold()).sum(local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | d[30.9].d |
+      | d[27].i |
+      | d[33.4].d |
+      | d[35.2].d |
\ No newline at end of file
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Trim.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Trim.feature
index feda185..6279ca4 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Trim.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Trim.feature
@@ -19,11 +19,12 @@
 Feature: Step - trim()
 
   @GraphComputerVerificationInjectionNotSupported
+  # This verifies both ASCII control space and ideographic space character \u3000 are property trimmed.
   Scenario: g_injectX__feature___test__nullX_trim
     Given the empty graph
     And the traversal of
       """
-      g.inject("  feature  ", " one test ", null, "", " ").trim()
+      g.inject("  feature  ", " one test ", null, "", " ", " abc", "abc ", " abc ", "  ").trim()
       """
     When iterated to list
     Then the result should be unordered
@@ -33,6 +34,23 @@
       | null |
       | str[] |
       | str[] |
+      | str[abc] |
+      | str[abc] |
+      | str[abc] |
+      | str[] |
+
+  @GraphComputerVerificationInjectionNotSupported
+  # This verifies both ASCII control space and ideographic space character \u3000 are property trimmed.
+  Scenario: g_injectX__feature___test__nullX_trimXlocalX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(["  feature  ", " one test ", null, "", " ", " abc", "abc ", " abc ", "  "]).trim(Scope.local)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[str[feature],str[one test],null,str[],str[],str[abc],str[abc],str[abc],str[]] |
 
   @GraphComputerVerificationInjectionNotSupported
   Scenario: g_injectXListXa_bXX_trim