You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by xi...@apache.org on 2023/09/18 23:54:18 UTC

[tinkerpop] branch master updated: add replace(), split(), and substring() string functions (#2232)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 88a75a795c add replace(), split(), and substring() string functions (#2232)
88a75a795c is described below

commit 88a75a795cc906a580e18e9884aa66e649218bac
Author: Yang Xia <55...@users.noreply.github.com>
AuthorDate: Mon Sep 18 16:54:11 2023 -0700

    add replace(), split(), and substring() string functions (#2232)
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/dev/provider/gremlin-semantics.asciidoc   |  83 ++++++++++++++++
 docs/src/reference/the-traversal.asciidoc          |  66 ++++++++++++-
 docs/src/upgrade/release-3.7.x.asciidoc            |  72 ++++++++++++++
 .../grammar/DefaultGremlinBaseVisitor.java         |  20 ++++
 .../language/grammar/TraversalMethodVisitor.java   |  34 +++++++
 .../traversal/dsl/graph/GraphTraversal.java        |  70 +++++++++++++-
 .../gremlin/process/traversal/dsl/graph/__.java    |  28 ++++++
 .../process/traversal/step/map/ReplaceStep.java    |  67 +++++++++++++
 .../process/traversal/step/map/SplitStep.java      |  67 +++++++++++++
 .../process/traversal/step/map/SubstringStep.java  | 107 +++++++++++++++++++++
 .../process/traversal/util/BytecodeHelper.java     |   6 ++
 .../grammar/TraversalMethodVisitorTest.java        |  20 ++++
 .../traversal/step/map/ReplaceStepTest.java        |  59 ++++++++++++
 .../process/traversal/step/map/SplitStepTest.java  |  70 ++++++++++++++
 .../traversal/step/map/SubstringStepTest.java      |  62 ++++++++++++
 .../Process/Traversal/GraphTraversal.cs            |  45 ++++++++-
 .../src/Gremlin.Net/Process/Traversal/__.cs        |  44 +++++++--
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  15 +++
 gremlin-go/driver/anonymousTraversal.go            |  21 ++++
 gremlin-go/driver/cucumber/gremlin.go              |  15 +++
 gremlin-go/driver/graphTraversal.go                |  18 ++++
 .../lib/process/graph-traversal.js                 |  33 +++++++
 .../gremlin-javascript/test/cucumber/gremlin.js    |  15 +++
 gremlin-language/src/main/antlr4/Gremlin.g4        |  16 +++
 .../gremlin_python/process/graph_traversal.py      |  42 ++++++++
 gremlin-python/src/main/python/radish/gremlin.py   |  15 +++
 .../gremlin/test/features/map/Replace.feature      |  69 +++++++++++++
 .../gremlin/test/features/map/Split.feature        |  85 ++++++++++++++++
 .../gremlin/test/features/map/Substring.feature    |  94 ++++++++++++++++++
 30 files changed, 1345 insertions(+), 14 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 5732ebac92..b12d1337ec 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,7 @@ This release also includes changes from <<release-3-6-5, 3.6.6>> and <<release-3
 * Added the `asString()`, `length()`, `toLower()`, and `toUpper()` steps to perform `String` manipulations.
 * Added Gherkin parsing support for specific string results using `str[]`.
 * Added the `trim()`, `lTrim()`, `rTrim()`, and `reverse()` steps to perform `String` manipulations.
+* Added `replace()`, `split()` and `substring()` steps to perform `String` manipulations.
 
 
 [[release-3-7.0]]
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc
index bc4e850474..d24339d6a7 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -941,6 +941,35 @@ resolve to a `Map`.
 See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java[source],
 link:https://tinkerpop.apache.org/docs/x.y.z/reference/#mergev-step[reference]
 
+[[replace-step]]
+=== replace()
+
+*Description:* Returns a string with the specified characters in the original string replaced with the new characters.
+
+*Syntax:* `replace(String, String)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`String` |`String`
+|=========================================================
+
+*Arguments:*
+
+* `String` - The string character(s) in the original string to be replaced. Nullable, a null input will be a no-op and
+the original string will be returned
+* `String` - The string character(s) to replace with. Nullable, a null input will be a no-op and the original string
+will be returned
+
+Null values from the incoming traverser are not processed and remain as null when returned.
+
+*Exceptions*
+* If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#replace-step[reference]
+
+
 === reverse()
 
 *Description:* Returns a reversed string of the incoming string traverser
@@ -992,6 +1021,60 @@ Null values from the incoming traverser are not processed and remain as null whe
 See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimStep.java[source],
 link:https://tinkerpop.apache.org/docs/x.y.z/reference/#rTrim-step[reference]
 
+[[split-step]]
+=== split()
+
+*Description:* Returns a list of strings created by splitting the incoming string traverser around the matches of the given separator.
+
+*Syntax:* `split(String)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`String` |`List`
+|=========================================================
+
+*Arguments:*
+
+* `String` - The string character(s) used as delimiter to split the input string. Nullable, a null separator will split on
+whitespaces.
+
+Null values from the incoming traverser are not processed and remain as null when returned.
+
+*Exceptions*
+* If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#split-step[reference]
+
+[[substring-step]]
+=== substring()
+
+*Description:* Returns a substring of the incoming string traverser with a 0-based start index (inclusive) and length specified.
+
+*Syntax:* `substring(long, long)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`String` |`String`
+|=========================================================
+
+*Arguments:*
+
+* `int` - The start index, 0 based. If the start index is negative then it will begin at the specified index counted
+from the end of the string, or 0 if it exceeds the string length.
+* `int` - The number of characters to return. Optional, if it is not specific or if it exceeds the length of the string
+then all remaining characters will be returned. Length &leq; 0 will return the empty string.
+
+Null values from the incoming traverser are not processed and remain as null when returned.
+
+*Exceptions*
+* If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#substring-step[reference]
+
 [[toLower-step]]
 === toLower()
 
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index d585b8095d..d6ea05dcab 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -3625,6 +3625,26 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
 
 link:++https://tinkerpop.apache.org/docs/x.y.z/recipes/#looping[`Looping Recipes`]
 
+[[replace-step]]
+=== Replace Step
+The `replace()`-step (*map*) returns a string with the specified characters in the original string replaced with the new
+characters. Any null arguments will be a no-op and the original string is returned. Null values from the incoming
+traversers are not processed and remain as null when returned. If the incoming traverser is a non-String  value then
+an IllegalArgumentException will be thrown.
+
+[gremlin-groovy,modern]
+----
+g.inject('that', 'this', 'test', null).replace('h', 'j') <1>
+g.V().hasLabel("software").values("name").replace("p", "g") <2>
+g.inject('hello world').replace(null, 'j') <3>
+----
+<1> Replace "h" in the strings with "j".
+<2> Return software names with "p" replaced by "g".
+<3> Null inputs are ignored and the original string is returned.
+
+*Additional References*
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#replace-java.lang.String-java.lang.String-++[`replace(String,String)`]
+
 [[reverse-step]]
 === Reverse Step
 
@@ -3651,7 +3671,6 @@ g.inject("   hello   ", " world ", null).rTrim()
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#rTrim--++[`rTrim()`]
 
-
 [[sack-step]]
 === Sack Step
 
@@ -4103,6 +4122,26 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#skip-org.apache.tinkerpop.gremlin.process.traversal.Scope-long-++[`skip(Scope,long)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
 
+[[split-step]]
+=== Split Step
+The `split()`-step (*map*) returns a list of strings created by splitting the incoming string traverser around the
+matches of the given separator. A null separator will split the string by whitespaces. Null values from the incoming
+traversers are not processed and remain as null when returned. If the incoming traverser is a non-String value then an
+IllegalArgumentException will be thrown.
+
+[gremlin-groovy,modern]
+----
+g.inject("that", "this", "test", null).split("h") <1>
+g.V().hasLabel("person").values("name").split("a") <2>
+g.inject("helloworld", "hello world", "hello   world").split(null) <3>
+----
+<1> Split the strings by "h".
+<2> Split person names by "a".
+<3> Splitting by null will split by whitespaces.
+
+*Additional References*
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#split-java.lang.String-++[`split(String)`]
+
 [[subgraph-step]]
 === Subgraph Step
 
@@ -4188,6 +4227,30 @@ List<Vertex> vertices = g.V(v).out().toList();
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#subgraph-java.lang.String-++[`subgraph(String)`]
 
+[[substring-step]]
+=== Substring Step
+The `substring()`-step (*map*) returns a substring with a 0-based start index (inclusive) index and length specified.
+If the start index is negative then it will begin at the specified index counted from the end of the string, or 0 if
+exceeding the string length.
+
+Length is optional, if it is not specific or if it exceeds the length of the string then all remaining characters will
+be returned. Length &leq; 0 will return the empty string. Null values are not processed and remain as null
+when returned. If the incoming traverser is a non-String value then an `IllegalArgumentException` will be thrown.
+
+[gremlin-groovy,modern]
+----
+g.inject("test", "hello world", null).substring(1, 8)
+g.inject("hello world").substring(-4) <1>
+g.inject("hello world").substring(2, 0) <2>
+g.V().hasLabel("software").values("name").substring(2)
+----
+<1> Negative start index, the first character is read by counting from the end of the string
+<2> Length of 0 specified will return the empty string
+
+*Additional References*
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#substring-int-++[`substring(int)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#substring-int-int-++[`substring(int,int)`]
+
 [[sum-step]]
 === Sum Step
 
@@ -4410,7 +4473,6 @@ g.inject("   hello   ", " world ", null).trim()
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#trim--++[`trim()`]
 
-
 [[unfold-step]]
 === Unfold Step
 
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc
index 0ffdc7125d..d905efd784 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -141,6 +141,78 @@ See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#rTrim-step[rTrim()-
 See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#reverse-step[reverse()-step]
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2672[TINKERPOP-2672]
 
+===== String Steps replace(), split(), substring()
+
+The following example demonstrates the use of a closure to perform `replace()` and `split()` functions:
+[source,text]
+----
+gremlin> g.V().hasLabel("software").values("name").map{it.get().replace("p", "g")}
+==>log
+==>riggle
+gremlin> g.V().hasLabel("person").values("name").map{it.get().split("a")}
+==>[m, rko]
+==>[v, d, s]
+==>[josh]
+==>[peter]
+----
+With these additional steps this operation can be performed with standard Gremlin syntax:
+[source,text]
+----
+gremlin> g.V().hasLabel("software").values("name").replace("p", "g")
+==>log
+==>riggle
+gremlin> g.V().hasLabel("person").values("name").split("a")
+==>[m,rko]
+==>[v,d,s]
+==>[josh]
+==>[peter]
+----
+
+For `substring()`, the new Gremlin step follows the SQL standard, taking parameters start index and desired length of substring,
+instead of start and end indices like Java/Groovy, enabling certain operations that would be complex to achieve with closure:
+[source,text]
+----
+gremlin> g.V().hasLabel("person").values("name").map{it.get().substring(1,3)}
+==>ar
+==>ad
+==>os
+==>et
+gremlin> g.V().hasLabel("person").values("name").map{it.get().substring(1)}
+==>arko
+==>adas
+==>osh
+==>eter
+gremlin> g.V().hasLabel("person").values("name").map{it.get().substring(-2)}
+String index out of range: -2
+Type ':help' or ':h' for help.
+----
+
+The `substring()`-step will begin at the start index and return a substring with the length specified. Negative start
+indices are allowed and will begin at the specified index counted from the end of the string:
+[source,text]
+----
+gremlin> g.V().hasLabel("person").values("name").substring(1,3)
+==>ark
+==>ada
+==>osh
+==>ete
+gremlin> g.V().hasLabel("person").values("name").substring(1)
+==>arko
+==>adas
+==>osh
+==>eter
+gremlin> g.V().hasLabel("person").values("name").substring(-2)
+==>ko
+==>as
+==>sh
+==>er
+----
+
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#replace-step[replace()-step]
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#split-step[split()-step]
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#substring-step[substring()-step]
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2672[TINKERPOP-2672]
+
 === Upgrading for Providers
 
 ==== Graph System Providers
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index ecdfb94b7e..857a80dc80 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
@@ -957,6 +957,26 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im
 	 */
 	@Override
 	public T visitTraversalMethod_reverse_Empty(final GremlinParser.TraversalMethod_reverse_EmptyContext ctx) { notImplemented(ctx); return null; }
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_replace_String_String(final GremlinParser.TraversalMethod_replace_String_StringContext ctx) { notImplemented(ctx); return null; }
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_split_String(final GremlinParser.TraversalMethod_split_StringContext ctx) { notImplemented(ctx); return null; }
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_substring_int(final GremlinParser.TraversalMethod_substring_intContext ctx) { notImplemented(ctx); return null; }
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_substring_int_int(final GremlinParser.TraversalMethod_substring_int_intContext ctx) { notImplemented(ctx); return null; }
 	/**
 	 * {@inheritDoc}
 	 */
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index 26fccd341e..937c14396c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -1805,6 +1805,40 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
         return graphTraversal.reverse();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_replace_String_String(final GremlinParser.TraversalMethod_replace_String_StringContext ctx) {
+        return graphTraversal.replace(antlr.argumentVisitor.parseString(ctx.stringNullableArgument(0)),
+                antlr.argumentVisitor.parseString(ctx.stringNullableArgument(1)));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_split_String(final GremlinParser.TraversalMethod_split_StringContext ctx) {
+        return graphTraversal.split(antlr.argumentVisitor.parseString(ctx.stringNullableArgument()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_substring_int(final GremlinParser.TraversalMethod_substring_intContext ctx) {
+        return graphTraversal.substring(antlr.argumentVisitor.parseNumber(ctx.integerArgument()).intValue());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_substring_int_int(final GremlinParser.TraversalMethod_substring_int_intContext ctx) {
+        return graphTraversal.substring(antlr.argumentVisitor.parseNumber(ctx.integerArgument(0)).intValue(),
+                antlr.argumentVisitor.parseNumber(ctx.integerArgument(1)).intValue());
+    }
+
     public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) {
         return ctx.nestedTraversalExpr().nestedTraversal()
                 .stream()
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index d9225c5506..43cd726dea 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -123,13 +123,16 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyKeyStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyMapStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyValueStep;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.RTrimStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.RangeLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReplaceStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReverseStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.RTrimStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SackStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SampleLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectOneStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SplitStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SubstringStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.TailLocalStep;
@@ -1552,6 +1555,68 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         return this.asAdmin().addStep(new ReverseStep<>(this.asAdmin()));
     }
 
+    /**
+     * Returns a string with the specified characters in the original string replaced with the new characters. Any null
+     * arguments will be a no-op and the original string is returned. Null values from incoming traversers are not
+     * processed and remain as null when returned. If the incoming traverser is a non-String value then an
+     * {@code IllegalArgumentException} will be thrown.
+     *
+     * @return the traversal with an appended {@link ReplaceStep}.
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#replace-step" target="_blank">Reference Documentation - Replace Step</a>
+     * @since 3.7.1
+     */
+    public default GraphTraversal<S, String> replace(final String oldChar, final String newChar) {
+        this.asAdmin().getBytecode().addStep(Symbols.replace, oldChar, newChar);
+        return this.asAdmin().addStep(new ReplaceStep<>(this.asAdmin(), oldChar, newChar));
+    }
+
+    /**
+     * Returns a list of strings created by splitting the incoming string traverser around the matches of the given separator.
+     * A null separator will split the string by whitespaces. Null values from incoming traversers are not processed
+     * and remain as null when returned. If the incoming traverser is a non-String value then an
+     * {@code IllegalArgumentException} will be thrown.
+     *
+     * @return the traversal with an appended {@link SplitStep}.
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#split-step" target="_blank">Reference Documentation - Split Step</a>
+     * @since 3.7.1
+     */
+    public default GraphTraversal<S, List<String>> split(final String separator) {
+        this.asAdmin().getBytecode().addStep(Symbols.split, separator);
+        return this.asAdmin().addStep(new SplitStep<>(this.asAdmin(), separator));
+    }
+
+    /**
+     * Returns a substring of the incoming string traverser with a 0-based start index (inclusive) specified,
+     * to the end of the string. If the start index is negative then it will begin at the specified index counted from the
+     * end of the string, or 0 if exceeding the string length. Null values are not processed and remain as null when returned.
+     * If the incoming traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+     *
+     * @return the traversal with an appended {@link SubstringStep}.
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#replace-step" target="_blank">Reference Documentation - Substring Step</a>
+     * @since 3.7.1
+     */
+    public default GraphTraversal<S, String> substring(final int startIndex) {
+        this.asAdmin().getBytecode().addStep(Symbols.substring, startIndex);
+        return this.asAdmin().addStep(new SubstringStep<>(this.asAdmin(), startIndex));
+    }
+
+    /**
+     * Returns a substring of the incoming string traverser with a 0-based start index (inclusive) index and length
+     * specified. If the start index is negative then it will begin at the specified index counted from the end
+     * of the string, or 0 if exceeding the string length. Length is optional, if it is not specific or if it exceeds
+     * the length of the string then all remaining characters will be returned. Length <= 0 will return the empty string.
+     * Null values are not processed and remain as null when returned. If the incoming traverser is a non-String value then an
+     * {@code IllegalArgumentException} will be thrown.
+     *
+     * @return the traversal with an appended {@link SubstringStep}.
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#replace-step" target="_blank">Reference Documentation - Substring Step</a>
+     * @since 3.7.1
+     */
+    public default GraphTraversal<S, String> substring(final int startIndex, final int length) {
+        this.asAdmin().getBytecode().addStep(Symbols.substring, startIndex, length);
+        return this.asAdmin().addStep(new SubstringStep<>(this.asAdmin(), startIndex, length));
+    }
+
     ///////////////////// FILTER STEPS /////////////////////
 
     /**
@@ -3623,6 +3688,9 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         public static final String lTrim = "lTrim";
         public static final String rTrim = "rTrim";
         public static final String reverse = "reverse";
+        public static final String replace = "replace";
+        public static final String substring = "substring";
+        public static final String split = "split";
 
         public static final String timeLimit = "timeLimit";
         public static final String simplePath = "simplePath";
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index ae5a607bbf..60fc630261 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -660,6 +660,34 @@ public class __ {
         return __.<A>start().reverse();
     }
 
+    /**
+     * @see GraphTraversal#replace(String, String)
+     */
+    public static <A> GraphTraversal<A, String> replace(final String oldChar, final String newChar) {
+        return __.<A>start().replace(oldChar, newChar);
+    }
+
+    /**
+     * @see GraphTraversal#split(String)
+     */
+    public static <A> GraphTraversal<A, List<String>> split(final String separator) {
+        return __.<A>start().split(separator);
+    }
+
+    /**
+     * @see GraphTraversal#substring(int)
+     */
+    public static <A> GraphTraversal<A, String> substring(final int startIndex) {
+        return __.<A>start().substring(startIndex);
+    }
+
+    /**
+     * @see GraphTraversal#substring(int, int)
+     */
+    public static <A> GraphTraversal<A, String> substring(final int startIndex, final int length) {
+        return __.<A>start().substring(startIndex, length);
+    }
+
     ///////////////////// FILTER STEPS /////////////////////
 
     /**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java
new file mode 100644
index 0000000000..32e1607685
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStep.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Reference implementation for substring step, a mid-traversal step which returns a string with the specified
+ * characters in the original string replaced with the new characters. Any null arguments will be a no-op and the
+ * original string is returned. Null values are not processed and remain as null when returned. If the incoming
+ * traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @author David Bechberger (http://bechberger.com)
+ * @author Yang Xia (http://github.com/xiazcy)
+ */
+public final class ReplaceStep<S> extends ScalarMapStep<S, String> implements TraversalParent {
+
+    private final String oldChar;
+    private final String newChar;
+
+    public ReplaceStep(final Traversal.Admin traversal, final String oldChar, final String newChar ) {
+        super(traversal);
+        this.oldChar=oldChar;
+        this.newChar=newChar;
+    }
+
+    @Override
+    protected String map(final Traverser.Admin<S> traverser) {
+        final S item = traverser.get();
+        // throws when incoming traverser isn't a string
+        if (null != item && !(item instanceof String)) {
+            throw new IllegalArgumentException(
+                    String.format("The replace() step can only take string as argument, encountered %s.", item.getClass()));
+        }
+
+        // we will pass null values to next step
+        return null == item? null : StringUtils.replace(((String) item), this.oldChar, this.newChar);
+    }
+
+    @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/SplitStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java
new file mode 100644
index 0000000000..b827d8e013
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStep.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Reference implementation for substring step, a mid-traversal step which returns a list of strings created by
+ * splitting the incoming string traverser around the matches of the given separator. A null separator will split the
+ * string by whitespaces. Null values from incoming traversers are not processed and remain as null when returned.
+ * If the incoming traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @author David Bechberger (http://bechberger.com)
+ * @author Yang Xia (http://github.com/xiazcy)
+ */
+public final class SplitStep<S> extends ScalarMapStep<S, List<String>> implements TraversalParent {
+
+    private final String separator;
+
+    public SplitStep(final Traversal.Admin traversal, final String separator) {
+        super(traversal);
+        this.separator = separator;
+    }
+
+    @Override
+    protected List<String> map(final Traverser.Admin<S> traverser) {
+        final S item = traverser.get();
+        // throws when incoming traverser isn't a string
+        if (null != item && !(item instanceof String)) {
+            throw new IllegalArgumentException(
+                    String.format("The split() step can only take string as argument, encountered %s.", item.getClass()));
+        }
+
+        // we will pass null values to next step
+        return null == item? null : Arrays.asList(StringUtils.splitByWholeSeparator((String) item, 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/SubstringStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java
new file mode 100644
index 0000000000..e28f40557e
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStep.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Reference implementation for substring step, a mid-traversal step which returns a substring of the incoming string
+ * traverser with a 0-based start index (inclusive) and length specified. If the start index is negative then it will
+ * begin at the specified index counted from the end of the string, or 0 if exceeding the string length.
+ * Length is optional, if it is not specific or if it exceeds the length of the string then all remaining characters will
+ * be returned. Length <= 0 will return the empty string. Null values are not processed and remain as null when returned.
+ * If the incoming traverser is a non-String value then an {@code IllegalArgumentException} will be thrown.
+ *
+ * @author David Bechberger (http://bechberger.com)
+ * @author Yang Xia (http://github.com/xiazcy)
+ */
+public final class SubstringStep<S> extends ScalarMapStep<S, String> {
+
+    private final Integer start;
+    private final Integer length;
+
+    public SubstringStep(final Traversal.Admin traversal, final Integer startIndex, final Integer length) {
+        super(traversal);
+        this.start = startIndex;
+        this.length = length;
+    }
+
+    public SubstringStep(final Traversal.Admin traversal, final Integer startIndex) {
+        this(traversal, startIndex, null);
+    }
+
+    @Override
+    protected String map(final Traverser.Admin<S> traverser) {
+        final S item = traverser.get();
+        // throws when incoming traverser isn't a string
+        if (null != item && !(item instanceof String)) {
+            throw new IllegalArgumentException(
+                    String.format("The substring() step can only take string as argument, encountered %s", item.getClass()));
+        }
+
+        // to preserve null items
+        final String strItem = (String) item;
+
+        if (null == strItem)
+            return null;
+
+        final int newStart = processStartIndex(strItem.length());
+        if (null == this.length)
+            return strItem.substring(newStart);
+
+        // length < 0 will return the empty string.
+        if (this.length <= 0)
+            return "";
+
+        // if length specified exceeds the string length it is assumed to be equal to the length, which returns all
+        // remaining characters in the string.
+        return strItem.substring(newStart, Math.min(this.length + newStart, strItem.length()));
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return Collections.singleton(TraverserRequirement.OBJECT);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + this.start.hashCode();
+        result = 31 * result + (null != this.length ? this.length.hashCode() : 0);
+        return result;
+    }
+
+    // Helper function to process the start index, if it is negative (which counts from end of string) it is converted
+    // to the positive index position or 0 when negative index exceeds the string length, if it is positive and exceeds
+    // the length of the string, it is assumed to equal to the length, which means an empty string will be returned.
+    private int processStartIndex(int strLen) {
+        if (this.start < 0) {
+            return Math.max(0, (strLen + this.start) % strLen);
+        } else {
+            return Math.min(this.start, strLen);
+        }
+    }
+
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java
index 484b10620f..8d539e1268 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java
@@ -106,11 +106,14 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyMapStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertyValueStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.RTrimStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.RangeLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReplaceStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReverseStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SackStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SampleLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectOneStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SplitStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.SubstringStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.TailLocalStep;
@@ -214,6 +217,9 @@ public final class BytecodeHelper {
             put(GraphTraversal.Symbols.lTrim, Collections.singletonList(LTrimStep.class));
             put(GraphTraversal.Symbols.rTrim, Collections.singletonList(RTrimStep.class));
             put(GraphTraversal.Symbols.reverse, Collections.singletonList(ReverseStep.class));
+            put(GraphTraversal.Symbols.replace, Collections.singletonList(ReplaceStep.class));
+            put(GraphTraversal.Symbols.substring, Collections.singletonList(SubstringStep.class));
+            put(GraphTraversal.Symbols.split, Collections.singletonList(SplitStep.class));
             put(GraphTraversal.Symbols.group, Arrays.asList(GroupStep.class, GroupSideEffectStep.class));
             put(GraphTraversal.Symbols.groupCount, Arrays.asList(GroupCountStep.class, GroupCountSideEffectStep.class));
             put(GraphTraversal.Symbols.tree, Arrays.asList(TreeStep.class, TreeSideEffectStep.class));
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
index 657dcc9d8a..f89d047c3b 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
@@ -1163,4 +1163,24 @@ public class TraversalMethodVisitorTest {
     public void shouldParseTraversalMethod_length_Empty() throws Exception {
         compare(g.V().length(), eval("g.V().length()"));
     }
+
+    @Test
+    public void shouldParseTraversalMethod_replace_string_string() throws Exception {
+        compare(g.V().replace("a", "b"), eval("g.V().replace('a', 'b')"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_split_string() throws Exception {
+        compare(g.V().split("a"), eval("g.V().split('a')"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_substring_long() throws Exception {
+        compare(g.V().substring(1), eval("g.V().substring(1)"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_substring_long_long() throws Exception {
+        compare(g.V().substring(1, 3), eval("g.V().substring(1, 3)"));
+    }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStepTest.java
new file mode 100644
index 0000000000..032eddeec3
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceStepTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import org.junit.Test;
+import org.mockito.internal.util.StringUtil;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ReplaceStepTest extends StepTest {
+
+    @Override
+    protected List<Traversal> getTraversals() {
+        return Collections.singletonList(__.replace("a", "b"));
+    }
+
+    @Test
+    public void testReturnTypes() {
+        assertEquals("halo", __.__("hello").replace("el", "a").next());
+        assertEquals("world", __.__("world").replace("el", "a").next());
+
+        assertEquals("hello", __.__("hello").replace(null, "a").next());
+        assertEquals("hello", __.__("hello").replace("el", null).next());
+        assertEquals("hello", __.__("hello").replace(null, null).next());
+
+        assertArrayEquals(new String[]{"hell0", "w0rld", null, ""},
+                __.__("hello", "world", null, "").replace("o", "0").toList().toArray());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldThrowWithIncomingArrayList() { __.__(Arrays.asList("a", "b", "c")).replace("a", "b").next(); }
+
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStepTest.java
new file mode 100644
index 0000000000..dafa63c5d2
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitStepTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import org.junit.Test;
+import org.mockito.internal.util.StringUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class SplitStepTest extends StepTest {
+
+    @Override
+    protected List<Traversal> getTraversals() {
+        return Collections.singletonList(__.split("a"));
+    }
+
+    @Test
+    public void testReturnTypes() {
+        assertArrayEquals(new String[]{"h", "llo"}, __.__("hello").split("e").next().toArray());
+        assertArrayEquals(new String[]{"ab", "bc"}, __.__("abc|abc").split("c|a").next().toArray());
+
+        assertArrayEquals(new String[]{"helloworld"}, __.__("helloworld").split(null).next().toArray());
+        assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split(null).next().toArray());
+        assertArrayEquals(new String[]{"hello", "world"}, __.__("hello   world").split(null).next().toArray());
+        assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split("").next().toArray());
+        assertArrayEquals(new String[]{"hello", "world"}, __.__("hello world").split(" ").next().toArray());
+        assertArrayEquals(new String[]{"hello world"}, __.__("hello world").split("  ").next().toArray());
+
+        List<List<String>> resList = new ArrayList<>();
+        resList.add(Arrays.asList("t", "is"));
+        resList.add(Arrays.asList("t", "at"));
+        resList.add(Collections.singletonList("test"));
+        resList.add(null);
+        resList.add(Collections.emptyList());
+        assertArrayEquals(resList.toArray(),
+                __.__("this", "that", "test", null, "").split("h").toList().toArray());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldThrowWithIncomingArrayList() {
+        __.__(Arrays.asList("a1a", "b1b", "c1c")).split("1").next();
+    }
+
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStepTest.java
new file mode 100644
index 0000000000..3af4e06e13
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringStepTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class SubstringStepTest extends StepTest {
+
+    @Override
+    protected List<Traversal> getTraversals() {
+        return Arrays.asList(
+                __.substring(1),
+                __.substring(1, 2));
+    }
+
+    @Test
+    public void testReturnTypes() {
+        assertEquals("ello wor", __.__("hello world").substring(1, 8).next());
+        assertEquals("ello", __.__("hello").substring(1, 8).next());
+        assertEquals("world", __.__("hello world").substring(6).next());
+
+        assertEquals("world", __.__("world").substring(-10).next());
+        assertEquals("orld", __.__("world").substring(-4).next());
+        assertEquals("orl", __.__("world").substring(-4, 3).next());
+        assertEquals("", __.__("world").substring(1, -1).next());
+
+        assertArrayEquals(new String[]{"st", "llo worl", null, ""},
+                __.__("test", "hello world", null, "").substring(2, 8).toList().toArray());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldThrowWithIncomingArrayList() {
+        __.__(Arrays.asList("aa", "bb", "cc")).substring(1).next();
+    }
+
+}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index d4e811db39..9a0a955700 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1178,7 +1178,7 @@ namespace Gremlin.Net.Process.Traversal
             Bytecode.AddStep("loops", loopName);
             return Wrap<TStart, int>(this);
         }
-        
+
         /// <summary>
         ///     Adds the lTrim step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
@@ -1653,7 +1653,16 @@ namespace Gremlin.Net.Process.Traversal
             Bytecode.AddStep("repeat", repeatTraversal);
             return Wrap<TStart, TEnd>(this);
         }
-        
+
+        /// <summary>
+        ///     Adds the replace step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<TStart, string?> Replace (string? oldChar, string? newChar)
+        {
+            Bytecode.AddStep("replace", oldChar, newChar);
+            return Wrap<TStart, string?>(this);
+        }
+
         /// <summary>
         ///     Adds the reverse step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
@@ -1662,7 +1671,7 @@ namespace Gremlin.Net.Process.Traversal
             Bytecode.AddStep("reverse");
             return Wrap<TStart, string?>(this);
         }
-        
+
         /// <summary>
         ///     Adds the rTrim step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
@@ -1835,6 +1844,15 @@ namespace Gremlin.Net.Process.Traversal
             return Wrap<TStart, TNewEnd>(this);
         }
 
+        /// <summary>
+        ///     Adds the split step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<TStart, List<string>?> Split (string? splitChar)
+        {
+            Bytecode.AddStep("split", splitChar);
+            return Wrap<TStart, List<string>?>(this);
+        }
+
         /// <summary>
         ///     Adds the store step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
@@ -1853,6 +1871,24 @@ namespace Gremlin.Net.Process.Traversal
             return Wrap<TStart, Edge>(this);
         }
 
+        /// <summary>
+        ///     Adds the subgraph step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<TStart, string?> Substring (int startIndex)
+        {
+            Bytecode.AddStep("substring", startIndex);
+            return Wrap<TStart, string?>(this);
+        }
+
+        /// <summary>
+        ///     Adds the subgraph step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<TStart, string?> Substring (int startIndex, int length)
+        {
+            Bytecode.AddStep("substring", startIndex, length);
+            return Wrap<TStart, string?>(this);
+        }
+
         /// <summary>
         ///     Adds the sum step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
@@ -2022,8 +2058,7 @@ namespace Gremlin.Net.Process.Traversal
             Bytecode.AddStep("tree", sideEffectKey);
             return Wrap<TStart, TEnd>(this);
         }
-        
-        
+
         /// <summary>
         ///     Adds the trim step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
index bcc24865ae..c1dc67e409 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
@@ -828,13 +828,13 @@ namespace Gremlin.Net.Process.Traversal
         {
             return new GraphTraversal<object, int>().Loops(loopName);            
         }
-        
+
         /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the lTrim step to that traversal.
         /// </summary>
         public static GraphTraversal<object, string?> LTrim()
         {
-            return new GraphTraversal<object, string?>().LTrim();            
+            return new GraphTraversal<object, string?>().LTrim();
         }
 
         /// <summary>
@@ -1136,13 +1136,21 @@ namespace Gremlin.Net.Process.Traversal
         {
             return new GraphTraversal<object, object>().Repeat(traversal);            
         }
-                
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the replace step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, string?> Replace(string? oldChar, string? newChar)
+        {
+            return new GraphTraversal<object, string?>().Replace(oldChar, newChar);
+        }
+
         /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the reverse step to that traversal.
         /// </summary>
         public static GraphTraversal<object, string?> Reverse()
         {
-            return new GraphTraversal<object, string?>().Reverse();            
+            return new GraphTraversal<object, string?>().Reverse();
         }
 
         /// <summary>
@@ -1150,7 +1158,7 @@ namespace Gremlin.Net.Process.Traversal
         /// </summary>
         public static GraphTraversal<object, string?> RTrim()
         {
-            return new GraphTraversal<object, string?>().RTrim();            
+            return new GraphTraversal<object, string?>().RTrim();
         }
 
         /// <summary>
@@ -1289,6 +1297,14 @@ namespace Gremlin.Net.Process.Traversal
             return new GraphTraversal<object, E2>().Skip<E2>(skip);            
         }
 
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the split step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, List<string>?> Split(string? splitChar)
+        {
+            return new GraphTraversal<object, List<string>?>().Split(splitChar);
+        }
+
         /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the store step to that traversal.
         /// </summary>
@@ -1305,6 +1321,22 @@ namespace Gremlin.Net.Process.Traversal
             return new GraphTraversal<object, Edge>().Subgraph(sideEffectKey);            
         }
 
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the substring step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, string?> Substring(int startIndex)
+        {
+            return new GraphTraversal<object, string?>().Substring(startIndex);
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the substring step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, string?> Substring(int startIndex, int length)
+        {
+            return new GraphTraversal<object, string?>().Substring(startIndex, length);
+        }
+
         /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the sum step to that traversal.
         /// </summary>
@@ -1434,7 +1466,7 @@ namespace Gremlin.Net.Process.Traversal
         /// </summary>
         public static GraphTraversal<object, string?> Trim()
         {
-            return new GraphTraversal<object, string?>().Trim();            
+            return new GraphTraversal<object, string?>().Trim();
         }
 
         /// <summary>
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 60beb195cc..93b5a6a11c 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -847,6 +847,10 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"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_V_valuesXnameX_rTrim", 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"," [...]
+               {"g_injectXthat_this_testX_replaceXh_jX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("that","this","test",null).Replace("h","j")}}, 
+               {"g_injectXListXa_bXcX_replaceXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Replace("a","b")}}, 
+               {"g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Replace(null,"g")}}, 
+               {"g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Replace("p","g")}}, 
                {"g_injectXfeature_test_nullX_reverse", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("feature","test one",null).Reverse()}}, 
                {"g_injectXListXa_bXX_reverse", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Reverse()}}, 
                {"g_V_valuesXnameX_reverse", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Reverse()}}, 
@@ -932,6 +936,17 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("song","name","MIGHT AS WELL").ShortestPath().With("~tinkerpop.shortestPath.target",__.Has("song","name","MAYBE YOU KNOW HOW I FEEL")).With("~tinkerpop.shortestPath.edges",__.OutE("followedBy")).With("~tinkerpop.shortestPath.distance","weig [...]
                {"g_V_hasXname_markoX_shortestPath_maxDistanceX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name","marko").ShortestPath().With("~tinkerpop.shortestPath.maxDistance",1)}}, 
                {"g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name","vadas").ShortestPath().With("~tinkerpop.shortestPath.distance","weight").With("~tinkerpop.shortestPath.maxDistance",1.3)}}, 
+               {"g_injectXthat_this_testX_spiltXhX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("that","this","test",null).Split("h")}}, 
+               {"g_injectXhello_worldX_spiltXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject("hello world").Split(null)}}, 
+               {"g_injectXListXa_bXcX_splitXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Split("a")}}, 
+               {"g_V_hasLabelXpersonX_valueXnameX_splitXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").Split(null)}}, 
+               {"g_V_hasLabelXpersonX_valueXnameX_splitXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").Split("a")}}, 
+               {"g_injectXthat_this_testX_substringX1_8X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("test","hello world",null).Substring(1,8)}}, 
+               {"g_injectXListXa_bXcX_substringX1_1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Substring(1,1)}}, 
+               {"g_V_hasLabelXpersonX_valueXnameX_substringX2X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Substring(2)}}, 
+               {"g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Substring(1,3)}}, 
+               {"g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Values<object>("name").Substring(1,0)}}, 
+               {"g_V_hasLabelXpersonX_valueXnameX_substringXneg3X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").Substring(-3)}}, 
                {"g_V_age_sum", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Sum<object>()}}, 
                {"g_V_foo_sum", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Sum<object>()}}, 
                {"g_V_age_fold_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold().Sum<object>(Scope.Local)}}, 
diff --git a/gremlin-go/driver/anonymousTraversal.go b/gremlin-go/driver/anonymousTraversal.go
index 3b1da9cc6d..1336830411 100644
--- a/gremlin-go/driver/anonymousTraversal.go
+++ b/gremlin-go/driver/anonymousTraversal.go
@@ -219,6 +219,8 @@ type AnonymousTraversal interface {
 	Read(args ...interface{}) *GraphTraversal
 	// Repeat adds the repeat step to the GraphTraversal.
 	Repeat(args ...interface{}) *GraphTraversal
+	// Replace adds the replace step to the GraphTraversal.
+	Replace(args ...interface{}) *GraphTraversal
 	// Reverse adds the reverse step to the GraphTraversal.
 	Reverse(args ...interface{}) *GraphTraversal
 	// RTrim adds the rTrim step to the GraphTraversal.
@@ -237,10 +239,14 @@ type AnonymousTraversal interface {
 	SimplePath(args ...interface{}) *GraphTraversal
 	// Skip adds the skip step to the GraphTraversal.
 	Skip(args ...interface{}) *GraphTraversal
+	// Split adds the split step to the GraphTraversal.
+	Split(args ...interface{}) *GraphTraversal
 	// Store adds the store step to the GraphTraversal.
 	Store(args ...interface{}) *GraphTraversal
 	// Subgraph adds the subgraph step to the GraphTraversal.
 	Subgraph(args ...interface{}) *GraphTraversal
+	// Substring adds the substring step to the GraphTraversal.
+	Substring(args ...interface{}) *GraphTraversal
 	// Sum adds the sum step to the GraphTraversal.
 	Sum(args ...interface{}) *GraphTraversal
 	// Tail adds the tail step to the GraphTraversal.
@@ -733,6 +739,11 @@ func (anonymousTraversal *anonymousTraversal) Repeat(args ...interface{}) *Graph
 	return anonymousTraversal.graphTraversal().Repeat(args...)
 }
 
+// Replace adds the replace step to the GraphTraversal.
+func (anonymousTraversal *anonymousTraversal) Replace(args ...interface{}) *GraphTraversal {
+	return anonymousTraversal.graphTraversal().Replace(args...)
+}
+
 // Reverse adds the reverse step to the GraphTraversal.
 func (anonymousTraversal *anonymousTraversal) Reverse(args ...interface{}) *GraphTraversal {
 	return anonymousTraversal.graphTraversal().Reverse(args...)
@@ -778,6 +789,11 @@ func (anonymousTraversal *anonymousTraversal) Skip(args ...interface{}) *GraphTr
 	return anonymousTraversal.graphTraversal().Skip(args...)
 }
 
+// Split adds the split step to the GraphTraversal.
+func (anonymousTraversal *anonymousTraversal) Split(args ...interface{}) *GraphTraversal {
+	return anonymousTraversal.graphTraversal().Split(args...)
+}
+
 // Store adds the store step to the GraphTraversal.
 func (anonymousTraversal *anonymousTraversal) Store(args ...interface{}) *GraphTraversal {
 	return anonymousTraversal.graphTraversal().Store(args...)
@@ -788,6 +804,11 @@ func (anonymousTraversal *anonymousTraversal) Subgraph(args ...interface{}) *Gra
 	return anonymousTraversal.graphTraversal().Subgraph(args...)
 }
 
+// Substring adds the subgraph step to the GraphTraversal.
+func (anonymousTraversal *anonymousTraversal) Substring(args ...interface{}) *GraphTraversal {
+	return anonymousTraversal.graphTraversal().Substring(args...)
+}
+
 // Sum adds the sum step to the GraphTraversal.
 func (anonymousTraversal *anonymousTraversal) Sum(args ...interface{}) *GraphTraversal {
 	return anonymousTraversal.graphTraversal().Sum(args...)
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 4faf9c525e..6fea78109f 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -817,6 +817,10 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "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_V_valuesXnameX_rTrim": {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   ").Proper [...]
+    "g_injectXthat_this_testX_replaceXh_jX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("that", "this", "test", nil).Replace("h", "j")}}, 
+    "g_injectXListXa_bXcX_replaceXa_bX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Replace("a", "b")}}, 
+    "g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Replace(nil, "g")}}, 
+    "g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Replace("p", "g")}}, 
     "g_injectXfeature_test_nullX_reverse": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("feature", "test one", nil).Reverse()}}, 
     "g_injectXListXa_bXX_reverse": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Reverse()}}, 
     "g_V_valuesXnameX_reverse": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Reverse()}}, 
@@ -902,6 +906,17 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("song", "name", "MIGHT AS WELL").ShortestPath().With("~tinkerpop.shortestPath.target", gremlingo.T__.Has("song", "name", "MAYBE YOU KNOW HOW I FEEL")).With("~tinkerpop.shortestPath.edges", gremlingo.T__.OutE("followedBy")).With("~tinkerpo [...]
     "g_V_hasXname_markoX_shortestPath_maxDistanceX1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "marko").ShortestPath().With("~tinkerpop.shortestPath.maxDistance", 1)}}, 
     "g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "vadas").ShortestPath().With("~tinkerpop.shortestPath.distance", "weight").With("~tinkerpop.shortestPath.maxDistance", 1.3)}}, 
+    "g_injectXthat_this_testX_spiltXhX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("that", "this", "test", nil).Split("h")}}, 
+    "g_injectXhello_worldX_spiltXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("hello world").Split(nil)}}, 
+    "g_injectXListXa_bXcX_splitXa_bX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Split("a")}}, 
+    "g_V_hasLabelXpersonX_valueXnameX_splitXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values("name").Split(nil)}}, 
+    "g_V_hasLabelXpersonX_valueXnameX_splitXaX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values("name").Split("a")}}, 
+    "g_injectXthat_this_testX_substringX1_8X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("test", "hello world", nil).Substring(1, 8)}}, 
+    "g_injectXListXa_bXcX_substringX1_1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Substring(1, 1)}}, 
+    "g_V_hasLabelXpersonX_valueXnameX_substringX2X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Substring(2)}}, 
+    "g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Substring(1, 3)}}, 
+    "g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Values("name").Substring(1, 0)}}, 
+    "g_V_hasLabelXpersonX_valueXnameX_substringXneg3X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values("name").Substring(-3)}}, 
     "g_V_age_sum": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Sum()}}, 
     "g_V_foo_sum": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Sum()}}, 
     "g_V_age_fold_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Fold().Sum(gremlingo.Scope.Local)}}, 
diff --git a/gremlin-go/driver/graphTraversal.go b/gremlin-go/driver/graphTraversal.go
index 74a5e0c944..133dd29ab4 100644
--- a/gremlin-go/driver/graphTraversal.go
+++ b/gremlin-go/driver/graphTraversal.go
@@ -577,6 +577,12 @@ func (g *GraphTraversal) Repeat(args ...interface{}) *GraphTraversal {
 	return g
 }
 
+// Replace adds the replace step to the GraphTraversal.
+func (g *GraphTraversal) Replace(args ...interface{}) *GraphTraversal {
+	g.Bytecode.AddStep("replace", args...)
+	return g
+}
+
 // Reverse adds the reverse step to the GraphTraversal.
 func (g *GraphTraversal) Reverse(args ...interface{}) *GraphTraversal {
 	g.Bytecode.AddStep("reverse", args...)
@@ -632,6 +638,12 @@ func (g *GraphTraversal) Skip(args ...interface{}) *GraphTraversal {
 	return g
 }
 
+// Split adds the split step to the GraphTraversal.
+func (g *GraphTraversal) Split(args ...interface{}) *GraphTraversal {
+	g.Bytecode.AddStep("split", args...)
+	return g
+}
+
 // Store adds the store step to the GraphTraversal.
 func (g *GraphTraversal) Store(args ...interface{}) *GraphTraversal {
 	g.Bytecode.AddStep("store", args...)
@@ -644,6 +656,12 @@ func (g *GraphTraversal) Subgraph(args ...interface{}) *GraphTraversal {
 	return g
 }
 
+// Substring adds the substring step to the GraphTraversal.
+func (g *GraphTraversal) Substring(args ...interface{}) *GraphTraversal {
+	g.Bytecode.AddStep("substring", int32Args(args)...)
+	return g
+}
+
 // Sum adds the sum step to the GraphTraversal.
 func (g *GraphTraversal) Sum(args ...interface{}) *GraphTraversal {
 	g.Bytecode.AddStep("sum", args...)
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
index ddd3cf850c..a5aa1c6999 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
@@ -1232,6 +1232,16 @@ class GraphTraversal extends Traversal {
     return this;
   }
 
+  /**
+   * Graph traversal replace method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  replace(...args) {
+    this.bytecode.addStep('replace', args);
+    return this;
+  }
+
   /**
    * Graph traversal reverse method.
    * @param {...Object} args
@@ -1322,6 +1332,16 @@ class GraphTraversal extends Traversal {
     return this;
   }
 
+  /**
+   * Graph traversal split method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  split(...args) {
+    this.bytecode.addStep('split', args);
+    return this;
+  }
+
   /**
    * Graph traversal store method.
    * @param {...Object} args
@@ -1342,6 +1362,16 @@ class GraphTraversal extends Traversal {
     return this;
   }
 
+  /**
+   * Graph traversal subgraph method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  substring(...args) {
+    this.bytecode.addStep('substring', args);
+    return this;
+  }
+
   /**
    * Graph traversal sum method.
    * @param {...Object} args
@@ -1669,6 +1699,7 @@ const statics = {
   propertyMap: (...args) => callOnEmptyTraversal('propertyMap', args),
   range: (...args) => callOnEmptyTraversal('range', args),
   repeat: (...args) => callOnEmptyTraversal('repeat', args),
+  replace: (...args) => callOnEmptyTraversal('replace', args),
   reverse: (...args) => callOnEmptyTraversal('reverse', args),
   rTrim: (...args) => callOnEmptyTraversal('rTrim', args),
   sack: (...args) => callOnEmptyTraversal('sack', args),
@@ -1677,8 +1708,10 @@ const statics = {
   sideEffect: (...args) => callOnEmptyTraversal('sideEffect', args),
   simplePath: (...args) => callOnEmptyTraversal('simplePath', args),
   skip: (...args) => callOnEmptyTraversal('skip', args),
+  split: (...args) => callOnEmptyTraversal('split', args),
   store: (...args) => callOnEmptyTraversal('store', args),
   subgraph: (...args) => callOnEmptyTraversal('subgraph', args),
+  substring: (...args) => callOnEmptyTraversal('substring', args),
   sum: (...args) => callOnEmptyTraversal('sum', args),
   tail: (...args) => callOnEmptyTraversal('tail', args),
   timeLimit: (...args) => callOnEmptyTraversal('timeLimit', args),
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 8c1f8d0d4e..2fa4f7d787 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
@@ -837,6 +837,10 @@ const gremlins = {
     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_V_valuesXnameX_rTrim: [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 [...]
+    g_injectXthat_this_testX_replaceXh_jX: [function({g}) { return g.inject("that","this","test",null).replace("h","j") }], 
+    g_injectXListXa_bXcX_replaceXa_bX: [function({g, xx1}) { return g.inject(xx1).replace("a","b") }], 
+    g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX: [function({g}) { return g.V().hasLabel("software").values("name").replace(null,"g") }], 
+    g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX: [function({g}) { return g.V().hasLabel("software").values("name").replace("p","g") }], 
     g_injectXfeature_test_nullX_reverse: [function({g}) { return g.inject("feature","test one",null).reverse() }], 
     g_injectXListXa_bXX_reverse: [function({g, xx1}) { return g.inject(xx1).reverse() }], 
     g_V_valuesXnameX_reverse: [function({g}) { return g.V().values("name").reverse() }], 
@@ -922,6 +926,17 @@ const gremlins = {
     g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX: [function({g}) { return g.V().has("song","name","MIGHT AS WELL").shortestPath().with_("~tinkerpop.shortestPath.target",__.has("song","name","MAYBE YOU KNOW HOW I FEEL")).with_("~tinkerpop.shortestPath.edges",__.outE("followedBy")).with_("~tinkerpop.shortestPath.distance","weight") }], 
     g_V_hasXname_markoX_shortestPath_maxDistanceX1X: [function({g}) { return g.V().has("name","marko").shortestPath().with_("~tinkerpop.shortestPath.maxDistance",1) }], 
     g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X: [function({g}) { return g.V().has("name","vadas").shortestPath().with_("~tinkerpop.shortestPath.distance","weight").with_("~tinkerpop.shortestPath.maxDistance",1.3) }], 
+    g_injectXthat_this_testX_spiltXhX: [function({g}) { return g.inject("that","this","test",null).split("h") }], 
+    g_injectXhello_worldX_spiltXnullX: [function({g}) { return g.inject("hello world").split(null) }], 
+    g_injectXListXa_bXcX_splitXa_bX: [function({g, xx1}) { return g.inject(xx1).split("a") }], 
+    g_V_hasLabelXpersonX_valueXnameX_splitXnullX: [function({g}) { return g.V().hasLabel("person").values("name").split(null) }], 
+    g_V_hasLabelXpersonX_valueXnameX_splitXaX: [function({g}) { return g.V().hasLabel("person").values("name").split("a") }], 
+    g_injectXthat_this_testX_substringX1_8X: [function({g}) { return g.inject("test","hello world",null).substring(1,8) }], 
+    g_injectXListXa_bXcX_substringX1_1X: [function({g, xx1}) { return g.inject(xx1).substring(1,1) }], 
+    g_V_hasLabelXpersonX_valueXnameX_substringX2X: [function({g}) { return g.V().hasLabel("software").values("name").substring(2) }], 
+    g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X: [function({g}) { return g.V().hasLabel("software").values("name").substring(1,3) }], 
+    g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X: [function({g}) { return g.V().hasLabel("software").values("name").substring(1,0) }], 
+    g_V_hasLabelXpersonX_valueXnameX_substringXneg3X: [function({g}) { return g.V().hasLabel("person").values("name").substring(-3) }], 
     g_V_age_sum: [function({g}) { return g.V().values("age").sum() }], 
     g_V_foo_sum: [function({g}) { return g.V().values("foo").sum() }], 
     g_V_age_fold_sumXlocalX: [function({g}) { return g.V().values("age").fold().sum(Scope.local) }], 
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index d5b67666c9..8df663209c 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -292,6 +292,9 @@ traversalMethod
     | traversalMethod_lTrim
     | traversalMethod_rTrim
     | traversalMethod_reverse
+    | traversalMethod_replace
+    | traversalMethod_split
+    | traversalMethod_substring
     ;
 
 traversalMethod_V
@@ -850,6 +853,19 @@ traversalMethod_reverse
     : 'reverse' LPAREN RPAREN #traversalMethod_reverse_Empty
     ;
 
+traversalMethod_replace
+    : 'replace' LPAREN stringNullableArgument COMMA stringNullableArgument RPAREN #traversalMethod_replace_String_String
+    ;
+
+traversalMethod_split
+    : 'split' LPAREN stringNullableArgument RPAREN #traversalMethod_split_String
+    ;
+
+traversalMethod_substring
+    : 'substring' LPAREN integerArgument RPAREN #traversalMethod_substring_int
+    | 'substring' LPAREN integerArgument COMMA integerArgument RPAREN #traversalMethod_substring_int_int
+    ;
+
 /*********************************************
     ARGUMENT AND TERMINAL RULES
 **********************************************/
diff --git a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
index d10b6dba85..82a96c49c1 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
@@ -777,6 +777,10 @@ class GraphTraversal(Traversal):
         self.bytecode.add_step("repeat", *args)
         return self
 
+    def replace(self, *args):
+        self.bytecode.add_step("replace", *args)
+        return self
+
     def reverse(self, *args):
         self.bytecode.add_step("reverse", *args)
         return self
@@ -834,6 +838,10 @@ class GraphTraversal(Traversal):
         self.bytecode.add_step("skip", *args)
         return self
 
+    def split(self, *args):
+        self.bytecode.add_step("split", *args)
+        return self
+
     def store(self, *args):
         self.bytecode.add_step("store", *args)
         return self
@@ -842,6 +850,10 @@ class GraphTraversal(Traversal):
         self.bytecode.add_step("subgraph", *args)
         return self
 
+    def substring(self, *args):
+        self.bytecode.add_step("substring", *args)
+        return self
+
     def sum_(self, *args):
         self.bytecode.add_step("sum", *args)
         return self
@@ -1431,6 +1443,10 @@ class __(object, metaclass=MagicType):
     def repeat(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).repeat(*args)
 
+    @classmethod
+    def replace(cls, *args):
+        return cls.graph_traversal(None, None, Bytecode()).replace(*args)
+
     @classmethod
     def reverse(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).reverse(*args)
@@ -1479,6 +1495,10 @@ class __(object, metaclass=MagicType):
     def skip(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).skip(*args)
 
+    @classmethod
+    def split(cls, *args):
+        return cls.graph_traversal(None, None, Bytecode()).split(*args)
+
     @classmethod
     def store(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).store(*args)
@@ -1487,6 +1507,10 @@ class __(object, metaclass=MagicType):
     def subgraph(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).subgraph(*args)
 
+    @classmethod
+    def substring(cls, *args):
+        return cls.graph_traversal(None, None, Bytecode()).substring(*args)
+
     @classmethod
     def sum_(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).sum_(*args)
@@ -2051,6 +2075,10 @@ def repeat(*args):
     return __.repeat(*args)
 
 
+def replace(*args):
+    return __.replace(*args)
+
+
 def reverse(*args):
     return __.reverse(*args)
 
@@ -2091,6 +2119,10 @@ def skip(*args):
     return __.skip(*args)
 
 
+def split(*args):
+    return __.split(*args)
+
+
 def store(*args):
     return __.store(*args)
 
@@ -2099,6 +2131,10 @@ def subgraph(*args):
     return __.subgraph(*args)
 
 
+def substring(*args):
+    return __.substring(*args)
+
+
 def sum_(*args):
     return __.sum_(*args)
 
@@ -2375,6 +2411,8 @@ statics.add_static('range_', range_)
 
 statics.add_static('repeat', repeat)
 
+statics.add_static('replace', replace)
+
 statics.add_static('reverse', reverse)
 
 statics.add_static('rTrim', rTrim)
@@ -2395,10 +2433,14 @@ statics.add_static('simple_path', simple_path)
 
 statics.add_static('skip', skip)
 
+statics.add_static('split', split)
+
 statics.add_static('store', store)
 
 statics.add_static('subgraph', subgraph)
 
+statics.add_static('substring', substring)
+
 statics.add_static('sum_', sum_)
 
 statics.add_static('tail', tail)
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 14bd88d482..f73e75888f 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -818,6 +818,10 @@ world.gremlins = {
     'g_injectX__feature__X_rTrim': [(lambda g:g.inject('  feature  ').rTrim())], 
     'g_injectXListXa_bXX_rTrim': [(lambda g, xx1=None:g.inject(xx1).rTrim())], 
     'g_V_valuesXnameX_rTrim': [(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_( [...]
+    'g_injectXthat_this_testX_replaceXh_jX': [(lambda g:g.inject('that','this','test',None).replace('h','j'))], 
+    'g_injectXListXa_bXcX_replaceXa_bX': [(lambda g, xx1=None:g.inject(xx1).replace('a','b'))], 
+    'g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX': [(lambda g:g.V().hasLabel('software').name.replace(None,'g'))], 
+    'g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX': [(lambda g:g.V().hasLabel('software').name.replace('p','g'))], 
     'g_injectXfeature_test_nullX_reverse': [(lambda g:g.inject('feature','test one',None).reverse())], 
     'g_injectXListXa_bXX_reverse': [(lambda g, xx1=None:g.inject(xx1).reverse())], 
     'g_V_valuesXnameX_reverse': [(lambda g:g.V().name.reverse())], 
@@ -903,6 +907,17 @@ world.gremlins = {
     'g_V_hasXsong_name_MIGHT_AS_WELLX_shortestPath_targetXhasXsong_name_MAYBE_YOU_KNOW_HOW_I_FEELXX_edgesXoutEXfollowedByXX_distanceXweightX': [(lambda g:g.V().has('song','name','MIGHT AS WELL').shortestPath().with_('~tinkerpop.shortestPath.target',__.has('song','name','MAYBE YOU KNOW HOW I FEEL')).with_('~tinkerpop.shortestPath.edges',__.outE('followedBy')).with_('~tinkerpop.shortestPath.distance','weight'))], 
     'g_V_hasXname_markoX_shortestPath_maxDistanceX1X': [(lambda g:g.V().has('name','marko').shortestPath().with_('~tinkerpop.shortestPath.maxDistance',1))], 
     'g_V_hasXname_vadasX_shortestPath_distanceXweightX_maxDistanceX1_3X': [(lambda g:g.V().has('name','vadas').shortestPath().with_('~tinkerpop.shortestPath.distance','weight').with_('~tinkerpop.shortestPath.maxDistance',float(1.3)))], 
+    'g_injectXthat_this_testX_spiltXhX': [(lambda g:g.inject('that','this','test',None).split('h'))], 
+    'g_injectXhello_worldX_spiltXnullX': [(lambda g:g.inject('hello world').split(None))], 
+    'g_injectXListXa_bXcX_splitXa_bX': [(lambda g, xx1=None:g.inject(xx1).split('a'))], 
+    'g_V_hasLabelXpersonX_valueXnameX_splitXnullX': [(lambda g:g.V().hasLabel('person').name.split(None))], 
+    'g_V_hasLabelXpersonX_valueXnameX_splitXaX': [(lambda g:g.V().hasLabel('person').name.split('a'))], 
+    'g_injectXthat_this_testX_substringX1_8X': [(lambda g:g.inject('test','hello world',None).substring(1,8))], 
+    'g_injectXListXa_bXcX_substringX1_1X': [(lambda g, xx1=None:g.inject(xx1).substring(1,1))], 
+    'g_V_hasLabelXpersonX_valueXnameX_substringX2X': [(lambda g:g.V().hasLabel('software').name.substring(2))], 
+    'g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X': [(lambda g:g.V().hasLabel('software').name.substring(1,3))], 
+    'g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X': [(lambda g:g.V().hasLabel('software').name.substring(1,0))], 
+    'g_V_hasLabelXpersonX_valueXnameX_substringXneg3X': [(lambda g:g.V().hasLabel('person').name.substring(-3))], 
     'g_V_age_sum': [(lambda g:g.V().age.sum_())], 
     'g_V_foo_sum': [(lambda g:g.V().foo.sum_())], 
     'g_V_age_fold_sumXlocalX': [(lambda g:g.V().age.fold().sum_(Scope.local))], 
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Replace.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Replace.feature
new file mode 100644
index 0000000000..6c5abfb1a7
--- /dev/null
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Replace.feature
@@ -0,0 +1,69 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepReplace
+Feature: Step - replace()
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXthat_this_testX_replaceXh_jX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject("that", "this", "test", null).replace("h", "j")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | tjat |
+      | tjis |
+      | test |
+      | null |
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXListXa_bXcX_replaceXa_bX
+    Given the empty graph
+    And using the parameter xx1 defined as "l[a,b]"
+    And the traversal of
+      """
+      g.inject(xx1).replace("a", "b")
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of "The replace() step can only take string as argument"
+
+  Scenario: g_V_hasLabelXsoftwareX_valueXnameX_replaceXnull_iX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("software").values("name").replace(null, "g")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | lop |
+      | ripple |
+
+  Scenario: g_V_hasLabelXsoftwareX_valueXnameX_replaceXa_iX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("software").values("name").replace("p", "g")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | log |
+      | riggle |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Split.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Split.feature
new file mode 100644
index 0000000000..70477f2fc5
--- /dev/null
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Split.feature
@@ -0,0 +1,85 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepSplit
+Feature: Step - split()
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXthat_this_testX_spiltXhX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject("that", "this", "test", null).split("h")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[t,at] |
+      | l[t,is] |
+      | l[test] |
+      | null |
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXhello_worldX_spiltXnullX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject("hello world").split(null)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[hello,world] |
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXListXa_bXcX_splitXa_bX
+    Given the empty graph
+    And using the parameter xx1 defined as "l[a,b]"
+    And the traversal of
+      """
+      g.inject(xx1).split("a")
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of "The split() step can only take string as argument"
+
+  Scenario: g_V_hasLabelXpersonX_valueXnameX_splitXnullX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("person").values("name").split(null)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[marko] |
+      | l[vadas] |
+      | l[josh] |
+      | l[peter] |
+
+  Scenario: g_V_hasLabelXpersonX_valueXnameX_splitXaX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("person").values("name").split("a")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[m,rko] |
+      | l[v,d,s] |
+      | l[josh] |
+      | l[peter] |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Substring.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Substring.feature
new file mode 100644
index 0000000000..7aac365d6c
--- /dev/null
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Substring.feature
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepSubstring
+Feature: Step - substring()
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXthat_this_testX_substringX1_8X
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject("test", "hello world", null).substring(1, 8)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | est |
+      | ello wor |
+      | null |
+
+  @GraphComputerVerificationInjectionNotSupported
+  Scenario: g_injectXListXa_bXcX_substringX1_1X
+    Given the empty graph
+    And using the parameter xx1 defined as "l[aa,bb]"
+    And the traversal of
+      """
+      g.inject(xx1).substring(1, 1)
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of "The substring() step can only take string as argument"
+
+  Scenario: g_V_hasLabelXpersonX_valueXnameX_substringX2X
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("software").values("name").substring(2)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | p |
+      | pple |
+
+  Scenario: g_V_hasLabelXsoftwareX_valueXnameX_substringX1_3X
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("software").values("name").substring(1, 3)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | op |
+      | ipp |
+
+  Scenario: g_V_hasLabelXsoftwareX_valueXnameX_substringX1_0X
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("software").values("name").substring(1, 0)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | str[] |
+      | str[] |
+
+  Scenario: g_V_hasLabelXpersonX_valueXnameX_substringXneg3X
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasLabel("person").values("name").substring(-3)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | rko |
+      | osh |
+      | das |
+      | ter |