You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2022/01/25 21:39:24 UTC

[tinkerpop] 01/01: TINKERPOP-2681 mergeE() and mergeV()

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

spmallette pushed a commit to branch TINKERPOP-2681
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 59c3d50671d901a42e35fba6c92cf8419affe4b1
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Jan 12 10:12:16 2022 -0500

    TINKERPOP-2681 mergeE() and mergeV()
---
 CHANGELOG.asciidoc                                 |   2 +
 docs/src/dev/developer/for-committers.asciidoc     |   1 +
 docs/src/dev/io/graphbinary.asciidoc               |   5 +
 docs/src/dev/io/graphson.asciidoc                  | 190 ++++++------
 docs/src/upgrade/release-3.6.x.asciidoc            |  65 ++++
 .../tinkerpop/gremlin/jsr223/CoreImports.java      |   8 +-
 .../language/grammar/GenericLiteralVisitor.java    |  17 +-
 .../language/grammar/GremlinBaseVisitor.java       |  65 ++++
 .../language/grammar/TraversalMethodVisitor.java   |  76 +++++
 .../grammar/TraversalSourceSpawnMethodVisitor.java |  22 ++
 .../tinkerpop/gremlin/process/traversal/Merge.java |  46 +++
 .../{step/TraversalOptionParent.java => Pick.java} |  13 +-
 .../gremlin/process/traversal/Translator.java      |   9 +-
 .../traversal/dsl/graph/GraphTraversal.java        | 125 +++++++-
 .../traversal/dsl/graph/GraphTraversalSource.java  |  82 ++++-
 .../gremlin/process/traversal/dsl/graph/__.java    |  44 ++-
 .../traversal/step/TraversalOptionParent.java      |  12 +-
 .../process/traversal/step/branch/BranchStep.java  |   3 +-
 .../process/traversal/step/branch/ChooseStep.java  |   9 +-
 .../process/traversal/step/branch/UnionStep.java   |  10 +-
 .../process/traversal/step/map/MergeEdgeStep.java  | 340 +++++++++++++++++++++
 .../traversal/step/map/MergeVertexStep.java        | 293 ++++++++++++++++++
 .../process/traversal/step/util/event/Event.java   |   6 +-
 .../traversal/translator/DotNetTranslator.java     |  34 ++-
 .../traversal/translator/GroovyTranslator.java     |   8 +-
 .../traversal/translator/JavascriptTranslator.java |   4 +-
 .../traversal/translator/PythonTranslator.java     |   4 +-
 .../gremlin/structure/io/binary/DataType.java      |   1 +
 .../io/binary/TypeSerializerRegistry.java          |   6 +-
 .../structure/io/binary/types/EnumSerializer.java  |   6 +-
 .../structure/io/graphson/GraphSONModule.java      |  21 +-
 .../io/graphson/GraphSONTypeSerializerV2d0.java    |   6 +-
 .../io/graphson/GraphSONTypeSerializerV3d0.java    |   6 +-
 .../gremlin/structure/io/gryo/GryoVersion.java     |  13 +-
 .../language/grammar/TraversalEnumParserTest.java  |   4 +-
 .../traversal/step/branch/BranchStepTest.java      |   2 +-
 .../traversal/step/branch/ChooseStepTest.java      |   2 +-
 .../gremlin/structure/io/gryo/GryoMapperTest.java  |   7 +
 gremlin-dotnet/build/generate.groovy               |  10 +
 .../Process/Traversal/GraphTraversal.cs            |  64 ++++
 .../Process/Traversal/GraphTraversalSource.cs      |  54 +++-
 .../src/Gremlin.Net/Process/Traversal/Merge.cs     |  63 ++++
 .../src/Gremlin.Net/Process/Traversal/__.cs        |  48 +++
 .../Structure/IO/GraphBinary/DataType.cs           |   1 +
 .../IO/GraphBinary/TypeSerializerRegistry.cs       |   2 +
 .../IO/GraphBinary/Types/EnumSerializer.cs         |   6 +
 .../Structure/IO/GraphSON/GraphSONReader.cs        |   1 +
 .../Structure/IO/GraphSON/MergeDeserializer.cs     |  27 +-
 .../Gherkin/CommonSteps.cs                         |   9 +-
 .../Gherkin/GherkinTestRunner.cs                   |   5 +
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  29 +-
 .../Gherkin/IgnoreException.cs                     |   9 +-
 .../Structure/IO/GraphBinary/GraphBinaryTests.cs   |  15 +
 .../Structure/IO/GraphSON/GraphSONReaderTests.cs   |  12 +
 .../Structure/IO/GraphSON/GraphSONWriterTests.cs   |  13 +-
 .../gremlin/driver/message/RequestMessage.java     |   3 +-
 .../GraphBinaryReaderWriterRoundTripTest.java      |   4 +-
 .../ast/VarAsBindingASTTransformation.groovy       |   7 +-
 .../jsr223/GremlinGroovyScriptEngineTest.java      |   9 +-
 gremlin-javascript/build/generate.groovy           |  11 +
 .../lib/process/graph-traversal.js                 |  42 +++
 .../gremlin-javascript/lib/process/traversal.js    |   1 +
 .../test/cucumber/feature-steps.js                 |   8 +-
 .../gremlin-javascript/test/cucumber/gremlin.js    |  30 +-
 gremlin-language/src/main/antlr4/Gremlin.g4        |  35 +++
 .../language/corpus/DocumentationReader.java       |   2 +
 .../language/grammar/ReferenceGrammarTest.java     |  16 +-
 gremlin-python/build/generate.groovy               |  12 +-
 .../gremlin_python/process/graph_traversal.py      |  38 +++
 .../python/gremlin_python/process/traversal.py     |   5 +
 .../gremlin_python/structure/io/graphbinaryV1.py   |  10 +-
 .../src/main/python/radish/feature_steps.py        |   8 +-
 gremlin-python/src/main/python/radish/gremlin.py   |  31 +-
 .../tests/driver/test_driver_remote_connection.py  |   1 +
 .../tests/structure/io/test_graphbinaryV1.py       |   7 +-
 .../handler/WsGremlinBinaryRequestDecoder.java     |   1 +
 .../handler/WsGremlinTextRequestDecoder.java       |   1 +
 gremlin-test/features/map/MergeEdge.feature        | 201 ++++++++++++
 gremlin-test/features/map/MergeVertex.feature      | 283 +++++++++++++++++
 gremlin-test/features/sideEffect/Group.feature     |  22 +-
 gremlin-test/features/sideEffect/Inject.feature    |   7 +-
 .../tinkerpop/gremlin/features/StepDefinition.java |  30 +-
 .../process/ProcessLimitedStandardSuite.java       |  55 ----
 .../process/traversal/step/branch/BranchTest.java  |   4 +-
 .../process/traversal/step/branch/ChooseTest.java  |   6 +-
 .../decoration/EventStrategyProcessTest.java       | 132 ++++++++
 .../tinkerpop/gremlin/structure/io/Model.java      |   4 +-
 .../_3_2_3/manual-graphson-generator.groovy        |   5 +-
 .../_3_2_4/manual-graphson-generator.groovy        |   5 +-
 .../io/gryo/_3_2_3/manual-gryo-generator.groovy    |   8 +-
 .../io/gryo/_3_2_4/manual-gryo-generator.groovy    |   8 +-
 .../traversal/step/map/TinkerMergeEdgeStep.java    |  97 ++++++
 .../traversal/step/map/TinkerMergeVertexStep.java  |  88 ++++++
 .../optimization/TinkerMergeEVStepStrategy.java    |  61 ++++
 .../gremlin/tinkergraph/structure/TinkerGraph.java |   4 +-
 .../gremlin/tinkergraph/TinkerGraphWorld.java      |   1 +
 96 files changed, 2920 insertions(+), 318 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 191ba91..111110d 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Changed TinkerGraph to allow identifiers to be heterogeneous when filtering.
 * Prevented values of `T` to `property()` from being `null`.
 * Added `fail()` step.
+* Added `mergeV()` and `mergeE()` steps.
+* Moved `TraversalOptionParent.Pick` to it's own class as `Pick`.
 * Improved Gherkin test framework to allow for asserting traversal exceptions as a behavior.
 * Fixed query indentation for profile metrics where indent levels were not being respected.
 * `TraversalOpProcessor` no longer accepts a `String` representation of `Bytecode` for the "gremlin" argument which was left to support older versions of the drivers.
diff --git a/docs/src/dev/developer/for-committers.asciidoc b/docs/src/dev/developer/for-committers.asciidoc
index 20486b0..510c7e6 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -517,6 +517,7 @@ tag when one is necessary will cause provider tests to fail:
 
 * `@AllowNullPropertyValues` - The scenario requires that the graph be configured with `AllowNullPropertyValues` as
 `true` (meaning that it can store `null` values).
+* `@AllowUserSuppliedIds` - The scenario requires that the graph be configured with `UserSuppliedIds` as `true`.
 * `@GraphComputerVerificationInjectionNotSupported` - The scenario will not work on with `GraphComputer` because the
 `inject()` step is not supported.
 * `@GraphComputerVerificationMidVNotSupported` - The scenario will not work on with `GraphComputer` because the
diff --git a/docs/src/dev/io/graphbinary.asciidoc b/docs/src/dev/io/graphbinary.asciidoc
index ecec755..1ec9c3f 100644
--- a/docs/src/dev/io/graphbinary.asciidoc
+++ b/docs/src/dev/io/graphbinary.asciidoc
@@ -107,6 +107,7 @@ Changes to existing types require new revision.
 - `0x2b`: Tree
 - `0x2c`: Metrics
 - `0x2d`: TraversalMetrics
+- `0x2e`: Merge
 - `0xfe`: Unspecified null object
 - `0x00`: Custom
 
@@ -567,6 +568,10 @@ Where:
 - `{duration}` is a `Long` describing the duration in nanoseconds.
 - `{metrics}` is a `List` composed by `Metrics` items.
 
+==== Merge
+
+Format: a single `String` representing the enum value.
+
 ==== Custom
 
 A custom type, represented as a blob value.
diff --git a/docs/src/dev/io/graphson.asciidoc b/docs/src/dev/io/graphson.asciidoc
index 46c5a7d..a20dda5 100644
--- a/docs/src/dev/io/graphson.asciidoc
+++ b/docs/src/dev/io/graphson.asciidoc
@@ -2765,46 +2765,6 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
-==== Operator
-
-[source,json]
-----
-{
-  "@type" : "g:Operator",
-  "@value" : "sum"
-}
-----
-
-==== Order
-
-[source,json]
-----
-{
-  "@type" : "g:Order",
-  "@value" : "shuffle"
-}
-----
-
-==== Pick
-
-[source,json]
-----
-{
-  "@type" : "g:Pick",
-  "@value" : "any"
-}
-----
-
-==== Pop
-
-[source,json]
-----
-{
-  "@type" : "g:Pop",
-  "@value" : "all"
-}
-----
-
 ==== Lambda
 
 [source,json]
@@ -2819,6 +2779,16 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
+==== Merge
+
+[source,json]
+----
+{
+  "@type" : "g:Merge",
+  "@value" : "onMatch"
+}
+----
+
 ==== Metrics
 
 [source,json]
@@ -2879,6 +2849,26 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
+==== Operator
+
+[source,json]
+----
+{
+  "@type" : "g:Operator",
+  "@value" : "sum"
+}
+----
+
+==== Order
+
+[source,json]
+----
+{
+  "@type" : "g:Order",
+  "@value" : "shuffle"
+}
+----
+
 ==== P
 
 [source,json]
@@ -2998,6 +2988,26 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
+==== Pick
+
+[source,json]
+----
+{
+  "@type" : "g:Pick",
+  "@value" : "any"
+}
+----
+
+==== Pop
+
+[source,json]
+----
+{
+  "@type" : "g:Pop",
+  "@value" : "all"
+}
+----
+
 ==== Scope
 
 [source,json]
@@ -5149,56 +5159,6 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
-==== Operator
-
-[source,json]
-----
-{
-  "@type" : "g:Operator",
-  "@value" : "sum"
-}
-----
-
-==== Order
-
-[source,json]
-----
-{
-  "@type" : "g:Order",
-  "@value" : "shuffle"
-}
-----
-
-==== Pick
-
-[source,json]
-----
-{
-  "@type" : "g:Pick",
-  "@value" : "any"
-}
-----
-
-==== Pop
-
-[source,json]
-----
-{
-  "@type" : "g:Pop",
-  "@value" : "all"
-}
-----
-
-==== Pick
-
-[source,json]
-----
-{
-  "@type" : "g:Pick",
-  "@value" : "any"
-}
-----
-
 ==== Lambda
 
 [source,json]
@@ -5213,6 +5173,16 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
+==== Merge
+
+[source,json]
+----
+{
+  "@type" : "g:Merge",
+  "@value" : "onMatch"
+}
+----
+
 ==== Metrics
 
 [source,json]
@@ -5271,6 +5241,26 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
 }
 ----
 
+==== Operator
+
+[source,json]
+----
+{
+  "@type" : "g:Operator",
+  "@value" : "sum"
+}
+----
+
+==== Order
+
+[source,json]
+----
+{
+  "@type" : "g:Order",
+  "@value" : "shuffle"
+}
+----
+
 ==== P
 
 `P` expects a single value of a `List` of values. There is special handling for `List` values when it comes to `within`, `without`, `inside`, `outside` and `between`. For `inside`, `outside` and `between`, the expectation is that the collection contain two objects (the rest will be ignored) and those two objects become the arguments to those methods. For `within` and `without`, these methods will accept an arbitrary number of objects in the collection.
@@ -5405,6 +5395,26 @@ Please see <<_p,P>> for additional information on `within`.
 }
 ----
 
+==== Pick
+
+[source,json]
+----
+{
+  "@type" : "g:Pick",
+  "@value" : "any"
+}
+----
+
+==== Pop
+
+[source,json]
+----
+{
+  "@type" : "g:Pop",
+  "@value" : "all"
+}
+----
+
 ==== Scope
 
 [source,json]
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index 6166865..93fac41 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -29,6 +29,71 @@ Please see the link:https://github.com/apache/tinkerpop/blob/3.6.0/CHANGELOG.asc
 
 === Upgrading for Users
 
+==== mergeV() and mergeE()
+
+One of the most commonly used patterns in Gremlin is the use of `fold().coalesce(unfold(), ...)` to perform upsert-like
+functionality. While this pattern is quite flexible, it can also be confusing to new users and for certain use cases
+challenging to get the pattern correctly implemented. For providers, the pattern is difficult to properly optimize
+because it can branch into complexity quite quickly making it hard to identify a section of Gremlin for an upsert and
+therefore is not executed as efficiently as it might have been otherwise.
+
+The new `mergeV()` and `mergeE()` steps greatly simplify this pattern and as the pattern is condensed into a single
+step it should be straightforward for providers to optimize as part of their implementations. The following example
+demonstrates just how much easier implementing a basic upsert of a vertex has gotten:
+
+[source,text]
+----
+// prior to 3.6.0, use fold().coalesce(unfold(), ...)
+gremlin> g.V().has('person','name','vadas').has('age', 27).
+......1>   fold().
+......2>   coalesce(unfold().property('age',30),
+......3>            addV('person').property('name','vadas').property('age',27)).
+......4>   elementMap()
+==>[id:2,label:person,name:vadas,age:30]
+
+// 3.6.0
+gremlin> g.mergeV([(T.label): 'person', name:'vadas', age: 27]).
+......1>     option(onMatch, [age: 30]).
+......2>   elementMap()
+==>[id:2,label:person,name:vadas,age:30]
+----
+
+The `fold().coalesce(unfold(), ...)` pattern was even more complicated for upserting edges, but the following example
+demonstrates how much easier `mergeE()` is to follow:
+
+[source,text]
+----
+// prior to 3.6.0, use fold().coalesce(unfold(), ...)
+gremlin> g.V().has('person','name','vadas').as('v').
+......1>            V().has('software','name','ripple').
+......2>            coalesce(__.inE('created').where(outV().as('v')),
+......3>                     addE('created').from('v').property('weight',0.5)).
+......4>   elementMap()
+==>[id:0,label:created,IN:[id:5,label:software],OUT:[id:2,label:person],weight:0.5]
+
+// 3.6.0
+gremlin> ripple = g.V().has('software','name','ripple').next()
+==>v[5]
+gremlin> g.V().has('person','name','vadas').
+......1>   mergeE([(T.label):'created',(IN):ripple, weight: 0.5]).
+......2>   elementMap()
+==>[id:0,label:created,IN:[id:5,label:software],OUT:[id:2,label:person],weight:0.5]
+----
+
+For those currently using the `fold().coalesce(unfold(), ...)` pattern, there is no need to be concerned with
+incompatibility as a result of these new steps. That pattern is still perfectly usable and valid Gremlin, but whenever
+possible it would be best to migrate away from it as graph providers ramp up on 3.6.0 support and introduce important
+write optimizations that will make a big difference in performance.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2681[TINKERPOP-2681],
+link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergee-step[mergeE()-step],
+link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergev-step[mergeV()-step]
+
+==== Moved Pick
+
+`Pick` was formerly a nested class of `TraversalOptionParent`, but has now been promoted to being a class on its own
+in `org.apache.tinkerpop.gremlin.process.traversal.Pick`.
+
 ==== Consistent by() Behavior
 
 The `by()` modulator is critical to the usage of Gremlin. When used in conjunction with a step that supports it, the
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
index 194a192..2232ec2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
@@ -55,9 +55,11 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verifica
 import org.apache.tinkerpop.gremlin.process.remote.RemoteConnection;
 import org.apache.tinkerpop.gremlin.process.traversal.Bindings;
 import org.apache.tinkerpop.gremlin.process.traversal.IO;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
@@ -194,13 +196,14 @@ public final class CoreImports {
         CLASS_IMPORTS.add(VertexProperty.Cardinality.class);
         CLASS_IMPORTS.add(Column.class);
         CLASS_IMPORTS.add(Direction.class);
+        CLASS_IMPORTS.add(Merge.class);
         CLASS_IMPORTS.add(Operator.class);
         CLASS_IMPORTS.add(Order.class);
         CLASS_IMPORTS.add(Pop.class);
         CLASS_IMPORTS.add(Scope.class);
         CLASS_IMPORTS.add(T.class);
         CLASS_IMPORTS.add(TraversalOptionParent.class);
-        CLASS_IMPORTS.add(TraversalOptionParent.Pick.class);
+        CLASS_IMPORTS.add(Pick.class);
         CLASS_IMPORTS.add(P.class);
         CLASS_IMPORTS.add(TextP.class);
         CLASS_IMPORTS.add(WithOptions.class);
@@ -344,12 +347,13 @@ public final class CoreImports {
         Collections.addAll(ENUM_IMPORTS, VertexProperty.Cardinality.values());
         Collections.addAll(ENUM_IMPORTS, Column.values());
         Collections.addAll(ENUM_IMPORTS, Direction.values());
+        Collections.addAll(ENUM_IMPORTS, Merge.values());
         Collections.addAll(ENUM_IMPORTS, Operator.values());
         Collections.addAll(ENUM_IMPORTS, Order.values());
         Collections.addAll(ENUM_IMPORTS, Pop.values());
         Collections.addAll(ENUM_IMPORTS, Scope.values());
         Collections.addAll(ENUM_IMPORTS, T.values());
-        Collections.addAll(ENUM_IMPORTS, TraversalOptionParent.Pick.values());
+        Collections.addAll(ENUM_IMPORTS, Pick.values());
     }
 
     private CoreImports() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
index 336eecb..5aea5e0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
@@ -20,9 +20,9 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.apache.commons.text.StringEscapeUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
@@ -33,6 +33,7 @@ import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -81,6 +82,13 @@ public class GenericLiteralVisitor extends GremlinBaseVisitor<Object> {
     }
 
     /**
+     * Parse a map literal context and return the map literal
+     */
+    public static Map getMapLiteral(final GremlinParser.GenericLiteralMapContext mapLiteral) {
+        return (Map) (instance().visitGenericLiteralMap(mapLiteral));
+    }
+
+    /**
      * Parse a boolean literal context and return the boolean literal
      */
     public static boolean getBooleanLiteral(final GremlinParser.BooleanLiteralContext booleanLiteral) {
@@ -299,6 +307,11 @@ public class GenericLiteralVisitor extends GremlinBaseVisitor<Object> {
         return antlr.tvisitor.visitNestedTraversal(ctx);
     }
 
+    @Override
+    public Object visitStructureVertex(final GremlinParser.StructureVertexContext ctx) {
+        return StructureElementVisitor.instance().visitStructureVertex(ctx);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -464,7 +477,7 @@ public class GenericLiteralVisitor extends GremlinBaseVisitor<Object> {
      */
     @Override
     public Object visitTraversalOptionParent(final GremlinParser.TraversalOptionParentContext ctx) {
-        return TraversalEnumParser.parseTraversalEnumFromContext(TraversalOptionParent.Pick.class, ctx);
+        return TraversalEnumParser.parseTraversalEnumFromContext(Pick.class, ctx);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
index 18f42f1..8cc7807 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
@@ -1529,4 +1529,69 @@ public class GremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
 	public T visitStructureVertex(final GremlinParser.StructureVertexContext ctx) {
 		notImplemented(ctx); return null;
 	}
+
+	@Override
+	public T visitTraversalSourceSpawnMethod_mergeV_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeV_MapContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalSourceSpawnMethod_mergeV_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeV_TraversalContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_option_Merge_Map(GremlinParser.TraversalMethod_option_Merge_MapContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_option_Merge_Traversal(final GremlinParser.TraversalMethod_option_Merge_TraversalContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMerge(final GremlinParser.TraversalMergeContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_mergeV_Map(final GremlinParser.TraversalMethod_mergeV_MapContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_mergeV_Traversal(final GremlinParser.TraversalMethod_mergeV_TraversalContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_mergeE_Map(final GremlinParser.TraversalMethod_mergeE_MapContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_mergeE_Traversal(final GremlinParser.TraversalMethod_mergeE_TraversalContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalSourceSpawnMethod_mergeE_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeE_MapContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalSourceSpawnMethod_mergeE_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeE_TraversalContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_mergeV_empty(final GremlinParser.TraversalMethod_mergeV_emptyContext ctx) {
+		notImplemented(ctx); return null;
+	}
+
+	@Override
+	public T visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) {
+		notImplemented(ctx); return null;
+	}
 }
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 e866598..ed1ae19 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
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.language.grammar;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
@@ -88,16 +89,73 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
         return this.graphTraversal.addV(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeV_Map(final GremlinParser.TraversalMethod_mergeV_MapContext ctx) {
+        return this.graphTraversal.mergeV(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeV_Traversal(final GremlinParser.TraversalMethod_mergeV_TraversalContext ctx) {
+        return this.graphTraversal.mergeV(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Traversal visitTraversalMethod_mergeV_empty(final GremlinParser.TraversalMethod_mergeV_emptyContext ctx) {
+        return this.graphTraversal.mergeV();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Traversal visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) {
+        return this.graphTraversal.mergeE();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeE_Map(final GremlinParser.TraversalMethod_mergeE_MapContext ctx) {
+        return this.graphTraversal.mergeE(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeE_Traversal(final GremlinParser.TraversalMethod_mergeE_TraversalContext ctx) {
+        return this.graphTraversal.mergeE(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public GraphTraversal visitTraversalMethod_addE_Traversal(final GremlinParser.TraversalMethod_addE_TraversalContext ctx) {
         return this.graphTraversal.addE(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public GraphTraversal visitTraversalMethod_addV_Traversal(final GremlinParser.TraversalMethod_addV_TraversalContext ctx) {
         return this.graphTraversal.addV(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public GraphTraversal visitTraversalMethod_addE_String(final GremlinParser.TraversalMethod_addE_StringContext ctx) {
         final int childIndexOfParameterEdgeLabel = 2;
@@ -976,6 +1034,24 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
      * {@inheritDoc}
      */
     @Override
+    public GraphTraversal visitTraversalMethod_option_Merge_Map(final GremlinParser.TraversalMethod_option_Merge_MapContext ctx) {
+        return graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()),
+                (Map) new GenericLiteralVisitor(antlr).visitGenericLiteralMap(ctx.genericLiteralMap()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_option_Merge_Traversal(final GremlinParser.TraversalMethod_option_Merge_TraversalContext ctx) {
+        return this.graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()),
+                antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public GraphTraversal visitTraversalMethod_optional(final GremlinParser.TraversalMethod_optionalContext ctx) {
         return this.graphTraversal.optional(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
index 73e8612..f7d04f7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
@@ -22,6 +22,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 
+import java.util.Map;
+
 /**
  * Use a {@link GraphTraversalSource} as the source and returns a {@link GraphTraversal} object.
  */
@@ -114,4 +116,24 @@ public class TraversalSourceSpawnMethodVisitor extends GremlinBaseVisitor<GraphT
         }
         return graphTraversal;
     }
+
+    @Override
+    public GraphTraversal visitTraversalSourceSpawnMethod_mergeV_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeV_MapContext ctx) {
+        return this.traversalSource.mergeV(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+    }
+
+    @Override
+    public GraphTraversal visitTraversalSourceSpawnMethod_mergeV_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeV_TraversalContext ctx) {
+        return this.traversalSource.mergeV(anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    @Override
+    public GraphTraversal visitTraversalSourceSpawnMethod_mergeE_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeE_TraversalContext ctx) {
+        return this.traversalSource.mergeE(anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    @Override
+    public GraphTraversal visitTraversalSourceSpawnMethod_mergeE_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeE_MapContext ctx) {
+        return this.traversalSource.mergeE(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Merge.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Merge.java
new file mode 100644
index 0000000..a64d8ce
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Merge.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Element;
+
+import java.util.Map;
+
+/**
+ * Options relevant to upsert-like steps such as {@link GraphTraversalSource#mergeV(Map)}.
+ */
+public enum Merge {
+
+    /**
+     * Allows definition of the action to take when a merge operation ends up not matching the search criteria.
+     * Typically, this event means that an {@link Element} will be created.
+     *
+     * @since 3.6.0
+     */
+    onCreate,
+
+    /**
+     * Allows definition of the action to take when a merge operation ends up successfully matching the search criteria.
+     * Typically, this event means that the matched {@link Element} will be returned.
+     *
+     * @since 3.6.0
+     */
+    onMatch
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
similarity index 66%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
copy to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
index 6eb60a2..980c7dd 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
@@ -16,16 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.process.traversal.step;
-
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+package org.apache.tinkerpop.gremlin.process.traversal;
 
 /**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * A token used with {@code option()}.
  */
-public interface TraversalOptionParent<M, S, E> extends TraversalParent {
-
-    public static enum Pick {any, none}
-
-    public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption);
+public enum Pick {
+    any, none
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
index 30dccc4..0be7555 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
@@ -19,7 +19,6 @@
 
 package org.apache.tinkerpop.gremlin.process.traversal;
 
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -165,9 +164,9 @@ public interface Translator<S, T> {
             protected abstract String getSyntax(final VertexProperty.Cardinality o);
 
             /**
-             * Take the {@link TraversalOptionParent.Pick} argument and convert it to a string representation in the target language.
+             * Take the {@link Pick} argument and convert it to a string representation in the target language.
              */
-            protected abstract String getSyntax(final TraversalOptionParent.Pick o);
+            protected abstract String getSyntax(final Pick o);
 
             /**
              * Take the numeric argument and convert it to a string representation in the target language. Languages
@@ -308,8 +307,8 @@ public interface Translator<S, T> {
                     return script.append(getSyntax((SackFunctions.Barrier) object));
                 } else if (object instanceof VertexProperty.Cardinality) {
                     return script.append(getSyntax((VertexProperty.Cardinality) object));
-                } else if (object instanceof TraversalOptionParent.Pick) {
-                    return script.append(getSyntax((TraversalOptionParent.Pick) object));
+                } else if (object instanceof Pick) {
+                    return script.append(getSyntax((Pick) object));
                 } else if (object instanceof Enum) {
                     return produceScript((Enum<?>) object);
                 } else if (object instanceof Vertex) {
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 376ed6b..7a985dc 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
@@ -26,15 +26,18 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PeerPres
 import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ProgramVertexProgramStep;
 import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ShortestPathVertexProgramStep;
 import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.lambda.ColumnTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.lambda.FunctionTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.lambda.LoopTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.lambda.PredicateTraverser;
@@ -100,6 +103,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.NoOpBarrierStep;
@@ -1072,6 +1077,89 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     }
 
     /**
+     * Performs a merge (i.e. upsert) style operation for an {@link Vertex} using the incoming {@code Map} traverser as
+     * an argument. The {@code Map} represents search criteria and will match each of the supplied key/value pairs where
+     * the keys may be {@code String} property values or a value of {@link T}. If a match is not made it will use that
+     * search criteria to create the new {@link Vertex}.
+     *
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, Vertex> mergeV() {
+        this.asAdmin().getBytecode().addStep(Symbols.mergeV);
+        final MergeVertexStep<S> step = new MergeVertexStep<>(this.asAdmin(), false);
+        return this.asAdmin().addStep(step);
+    }
+
+    /**
+     * Performs a merge (i.e. upsert) style operation for an {@link Vertex} using a {@code Map} as an argument.
+     * The {@code Map} represents search criteria and will match each of the supplied key/value pairs where the keys
+     * may be {@code String} property values or a value of {@link T}. If a match is not made it will use that search
+     * criteria to create the new {@link Vertex}.
+     *
+     * @param searchCreate This {@code Map} can have a key of {@link T} or a {@code String}.
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, Vertex> mergeV(final Map<Object, Object> searchCreate) {
+        this.asAdmin().getBytecode().addStep(Symbols.mergeV, searchCreate);
+        final MergeVertexStep<S> step = new MergeVertexStep<>(this.asAdmin(), false, searchCreate);
+        return this.asAdmin().addStep(step);
+    }
+
+    /**
+     * Performs a merge (i.e. upsert) style operation for an {@link Vertex} using a {@code Map} as an argument.
+     * The {@code Map} represents search criteria and will match each of the supplied key/value pairs where the keys
+     * may be {@code String} property values or a value of {@link T}. If a match is not made it will use that search
+     * criteria to create the new {@link Vertex}.
+     *
+     *  @param searchCreate This anonymous {@link Traversal} must produce a {@code Map} that may have a keys of
+     *  {@link T} or a {@code String}.
+     *  @since 3.6.0
+     */
+    public default GraphTraversal<S, Vertex> mergeV(final Traversal<?, Map<Object, Object>> searchCreate) {
+        this.asAdmin().getBytecode().addStep(Symbols.mergeV, searchCreate);
+        final MergeVertexStep<S> step = new MergeVertexStep(this.asAdmin(), false, searchCreate.asAdmin());
+        return this.asAdmin().addStep(step);
+    }
+
+    /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using an
+     * incoming {@code Map} as an argument.
+     *
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, Edge> mergeE() {
+        this.asAdmin().getBytecode().addStep(Symbols.mergeE);
+        final MergeEdgeStep<S> step = new MergeEdgeStep(this.asAdmin(), false);
+        return this.asAdmin().addStep(step);
+    }
+
+    /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+     * {@code Map} as an argument.
+     *
+     * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, Edge> mergeE(final Map<?, Object> searchCreate) {
+        this.asAdmin().getBytecode().addStep(Symbols.mergeE, searchCreate);
+        final MergeEdgeStep<S> step = new MergeEdgeStep(this.asAdmin(), false, searchCreate);
+        return this.asAdmin().addStep(step);
+    }
+
+    /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+     * {@code Map} as an argument.
+     *
+     * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+     * @since 3.6.0
+     */
+    public default GraphTraversal<S, Edge> mergeE(final Traversal<?, Map<Object, Object>> searchCreate) {
+        this.asAdmin().getBytecode().addStep(Symbols.mergeE, searchCreate);
+        final MergeEdgeStep<S> step = new MergeEdgeStep(this.asAdmin(), false, searchCreate.asAdmin());
+        return this.asAdmin().addStep(step);
+    }
+
+    /**
      * Adds an {@link Edge} with the specified edge label.
      *
      * @param edgeLabel the label of the newly added edge
@@ -3058,18 +3146,39 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     ////
 
     /**
-     * This step modifies {@link #choose(Function)} to specifies the available choices that might be executed.
+     * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the
+     * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see
+     * the documentation of such steps to understand the usage context.
      *
-     * @param pick       the token that would trigger this option which may be a {@link TraversalOptionParent.Pick},
-     *                   a {@link Traversal}, {@link Predicate}, or object depending on the step being modulated.
+     * @param token       the token that would trigger this option which may be a {@link Pick}, {@link Merge},
+     *                    a {@link Traversal}, {@link Predicate}, or object depending on the step being modulated.
      * @param traversalOption the option as a traversal
      * @return the traversal with the modulated step
      * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#choose-step" target="_blank">Reference Documentation - Choose Step</a>
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a>
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a>
      * @since 3.0.0-incubating
      */
-    public default <M, E2> GraphTraversal<S, E> option(final M pick, final Traversal<?, E2> traversalOption) {
-        this.asAdmin().getBytecode().addStep(Symbols.option, pick, traversalOption);
-        ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addGlobalChildOption(pick, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+    public default <M, E2> GraphTraversal<S, E> option(final M token, final Traversal<?, E2> traversalOption) {
+        this.asAdmin().getBytecode().addStep(Symbols.option, token, traversalOption);
+        ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption(token, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+        return this;
+    }
+
+    /**
+     * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the
+     * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see
+     * the documentation of such steps to understand the usage context.
+     *
+     * @param m Provides a {@code Map} as the option which is the same as doing {@code constant(m)}.
+     * @return the traversal with the modulated step
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a>
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a>
+     * @since 3.6.0
+     */
+    public default <M, E2> GraphTraversal<S, E> option(final M token, final Map<Object, Object> m) {
+        this.asAdmin().getBytecode().addStep(Symbols.option, token, m);
+        ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption(token, (Traversal.Admin<E, E2>) new ConstantTraversal<>(m).asAdmin());
         return this;
     }
 
@@ -3083,7 +3192,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      */
     public default <E2> GraphTraversal<S, E> option(final Traversal<?, E2> traversalOption) {
         this.asAdmin().getBytecode().addStep(Symbols.option, traversalOption);
-        ((TraversalOptionParent<Object, E, E2>) this.asAdmin().getEndStep()).addGlobalChildOption(TraversalOptionParent.Pick.any, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+        ((TraversalOptionParent<Object, E, E2>) this.asAdmin().getEndStep()).addChildOption(Pick.any, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
         return this;
     }
 
@@ -3186,6 +3295,8 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         public static final String tree = "tree";
         public static final String addV = "addV";
         public static final String addE = "addE";
+        public static final String mergeV = "mergeV";
+        public static final String mergeE = "mergeE";
         public static final String from = "from";
         public static final String filter = "filter";
         public static final String or = "or";
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
index fbb873f..5806227 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
@@ -30,17 +30,22 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStartStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IoStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectStep;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.RequirementsStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Transaction;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
 
+import java.util.Map;
 import java.util.Optional;
 import java.util.function.BinaryOperator;
 import java.util.function.Supplier;
@@ -298,6 +303,8 @@ public class GraphTraversalSource implements TraversalSource {
     /**
      * Spawns a {@link GraphTraversal} by adding a vertex with the specified label. If the {@code label} is
      * {@code null} then it will default to {@link Vertex#DEFAULT_LABEL}.
+     *
+     * @since 3.1.0-incubating
      */
     public GraphTraversal<Vertex, Vertex> addV(final String vertexLabel) {
         if (null == vertexLabel) throw new IllegalArgumentException("vertexLabel cannot be null");
@@ -310,6 +317,8 @@ public class GraphTraversalSource implements TraversalSource {
     /**
      * Spawns a {@link GraphTraversal} by adding a vertex with the label as determined by a {@link Traversal}. If the
      * {@code vertexLabelTraversal} is {@code null} then it will default to {@link Vertex#DEFAULT_LABEL}.
+     *
+     * @since 3.3.1
      */
     public GraphTraversal<Vertex, Vertex> addV(final Traversal<?, String> vertexLabelTraversal) {
         if (null == vertexLabelTraversal) throw new IllegalArgumentException("vertexLabelTraversal cannot be null");
@@ -321,6 +330,8 @@ public class GraphTraversalSource implements TraversalSource {
 
     /**
      * Spawns a {@link GraphTraversal} by adding a vertex with the default label.
+     *
+     * @since 3.1.0-incubating
      */
     public GraphTraversal<Vertex, Vertex> addV() {
         final GraphTraversalSource clone = this.clone();
@@ -330,7 +341,9 @@ public class GraphTraversalSource implements TraversalSource {
     }
 
     /**
-     * Spawns a {@link GraphTraversal} by adding a edge with the specified label.
+     * Spawns a {@link GraphTraversal} by adding an edge with the specified label.
+     *
+     * @since 3.1.0-incubating
      */
     public GraphTraversal<Edge, Edge> addE(final String label) {
         final GraphTraversalSource clone = this.clone();
@@ -341,6 +354,8 @@ public class GraphTraversalSource implements TraversalSource {
 
     /**
      * Spawns a {@link GraphTraversal} by adding a edge with a label as specified by the provided {@link Traversal}.
+     *
+     * @since 3.3.1
      */
     public GraphTraversal<Edge, Edge> addE(final Traversal<?, String> edgeLabelTraversal) {
         final GraphTraversalSource clone = this.clone();
@@ -350,6 +365,67 @@ public class GraphTraversalSource implements TraversalSource {
     }
 
     /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Vertex} using a
+     * {@code Map} as an argument. The {@code Map} represents search criteria and will match each of the supplied
+     * key/value pairs where the keys may be {@code String} property values or a value of {@link T}. If a match is not
+     * made it will use that search criteria to create the new {@link Vertex}.
+     *
+     * @param searchCreate This {@code Map} can have a key of {@link T} or a {@code String}.
+     * @since 3.6.0
+     */
+    public GraphTraversal<Vertex, Vertex> mergeV(final Map<Object, Object> searchCreate) {
+        final GraphTraversalSource clone = this.clone();
+        clone.bytecode.addStep(GraphTraversal.Symbols.mergeV, searchCreate);
+        final GraphTraversal.Admin<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone);
+        return traversal.addStep(new MergeVertexStep(traversal, true, searchCreate));
+    }
+
+    /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Vertex} using a
+     * {@code Map} as an argument. The {@code Map} represents search criteria and will match each of the supplied
+     * key/value pairs where the keys may be {@code String} property values or a value of {@link T}. If a match is not
+     * made it will use that search criteria to create the new {@link Vertex}.
+     *
+     * @param searchCreate This anonymous {@link Traversal} must produce a {@code Map} that may have a keys of
+     * {@link T} or a {@code String}.
+     * @since 3.6.0
+     */
+    public <S> GraphTraversal<S, Vertex> mergeV(final Traversal<?, Map<Object, Object>> searchCreate) {
+        final GraphTraversalSource clone = this.clone();
+        clone.bytecode.addStep(GraphTraversal.Symbols.mergeV, searchCreate);
+        final GraphTraversal.Admin<S, Vertex> traversal = new DefaultGraphTraversal<>(clone);
+        return traversal.addStep(new MergeVertexStep(traversal, true, searchCreate.asAdmin()));
+    }
+
+    /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+     * {@code Map} as an argument.
+     *
+     * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+     * @since 3.6.0
+     */
+    public GraphTraversal<Edge, Edge> mergeE(final Map<?, Object> searchCreate) {
+        final GraphTraversalSource clone = this.clone();
+        clone.bytecode.addStep(GraphTraversal.Symbols.mergeE, searchCreate);
+        final GraphTraversal.Admin<Edge, Edge> traversal = new DefaultGraphTraversal<>(clone);
+        return traversal.addStep(new MergeEdgeStep(traversal, true, searchCreate));
+    }
+
+    /**
+     * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+     * {@code Map} as an argument.
+     *
+     * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+     * @since 3.6.0
+     */
+    public GraphTraversal<Edge, Edge> mergeE(final Traversal<?, Map<Object, Object>> searchCreate) {
+        final GraphTraversalSource clone = this.clone();
+        clone.bytecode.addStep(GraphTraversal.Symbols.mergeE, searchCreate);
+        final GraphTraversal.Admin<Edge, Edge> traversal = new DefaultGraphTraversal<>(clone);
+        return traversal.addStep(new MergeEdgeStep(traversal, true, searchCreate.asAdmin()));
+    }
+
+    /**
      * Spawns a {@link GraphTraversal} starting it with arbitrary values.
      */
     public <S> GraphTraversal<S, S> inject(S... starts) {
@@ -364,6 +440,8 @@ public class GraphTraversalSource implements TraversalSource {
     /**
      * Spawns a {@link GraphTraversal} starting with all vertices or some subset of vertices as specified by their
      * unique identifier.
+     *
+     * @since 3.0.0-incubating
      */
     public GraphTraversal<Vertex, Vertex> V(final Object... vertexIds) {
         // a single null is [null]
@@ -377,6 +455,8 @@ public class GraphTraversalSource implements TraversalSource {
     /**
      * Spawns a {@link GraphTraversal} starting with all edges or some subset of edges as specified by their unique
      * identifier.
+     *
+     * @since 3.0.0-incubating
      */
     public GraphTraversal<Edge, Edge> E(final Object... edgeIds) {
         // a single null is [null]
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 0502ed0..6e6374f 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
@@ -516,6 +516,27 @@ public class __ {
     }
 
     /**
+     * @see GraphTraversal#mergeV()
+     */
+    public static <A> GraphTraversal<A, Vertex> mergeV() {
+        return __.<A>start().mergeV();
+    }
+
+    /**
+     * @see GraphTraversal#mergeV(Map)
+     */
+    public static <A> GraphTraversal<A, Vertex> mergeV(final Map<Object, Object> searchCreate) {
+        return __.<A>start().mergeV(searchCreate);
+    }
+
+    /**
+     * @see GraphTraversal#mergeV(Traversal)
+     */
+    public static <A> GraphTraversal<A, Vertex> mergeV(final Traversal<?, Map<Object, Object>> searchCreate) {
+        return __.<A>start().mergeV(searchCreate);
+    }
+
+    /**
      * @see GraphTraversal#addE(String)
      */
     public static <A> GraphTraversal<A, Edge> addE(final String edgeLabel) {
@@ -523,13 +544,34 @@ public class __ {
     }
 
     /**
-     * @see GraphTraversal#addE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)
+     * @see GraphTraversal#addE(Traversal)
      */
     public static <A> GraphTraversal<A, Edge> addE(final Traversal<?, String> edgeLabelTraversal) {
         return __.<A>start().addE(edgeLabelTraversal);
     }
 
     /**
+     * @see GraphTraversal#mergeE()
+     */
+    public static <A> GraphTraversal<A, Edge> mergeE() {
+        return __.<A>start().mergeE();
+    }
+
+    /**
+     * @see GraphTraversal#mergeE(Map)
+     */
+    public static <A> GraphTraversal<A, Edge> mergeE(final Map<?, Object> searchCreate) {
+        return __.<A>start().mergeE(searchCreate);
+    }
+
+    /**
+     * @see GraphTraversal#mergeE(Traversal)
+     */
+    public static <A> GraphTraversal<A, Edge> mergeE(final Traversal<?, Map<Object, Object>> searchCreate) {
+        return __.<A>start().mergeE(searchCreate);
+    }
+
+    /**
      * @see GraphTraversal#math(String)
      */
     public static <A> GraphTraversal<A, Double> math(final String expression) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
index 6eb60a2..354700b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
@@ -18,14 +18,20 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 
 /**
+ * Describes steps that can be parent to a {@link Traversal} from the {@code option()} modulator.
+ *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
 public interface TraversalOptionParent<M, S, E> extends TraversalParent {
 
-    public static enum Pick {any, none}
-
-    public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption);
+    /**
+     * The child as defined by the token it takes, like {@link Pick} or {@link Merge}. This traversal may be of local
+     * or global scope depending on the step implementation that works with {@code option()}.
+     */
+    public void addChildOption(final M token, final Traversal.Admin<S, E> traversalOption);
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
index 361ed54..085cf57 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.lambda.PredicateTraversal;
@@ -63,7 +64,7 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     }
 
     @Override
-    public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
+    public void addChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
         if (pickToken instanceof Pick) {
             if (this.traversalPickOptions.containsKey(pickToken))
                 this.traversalPickOptions.get(pickToken).add(traversalOption);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
index a804d8f..86ec39e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.HasNextStep;
 
@@ -36,18 +37,18 @@ public final class ChooseStep<S, E, M> extends BranchStep<S, E, M> {
 
     public ChooseStep(final Traversal.Admin traversal, final Traversal.Admin<S, ?> predicateTraversal, final Traversal.Admin<S, E> trueChoice, final Traversal.Admin<S, E> falseChoice) {
         this(traversal, (Traversal.Admin<S, M>) predicateTraversal.addStep(new HasNextStep<>(predicateTraversal)));
-        this.addGlobalChildOption((M) Boolean.TRUE, trueChoice);
-        this.addGlobalChildOption((M) Boolean.FALSE, falseChoice);
+        this.addChildOption((M) Boolean.TRUE, trueChoice);
+        this.addChildOption((M) Boolean.FALSE, falseChoice);
     }
 
     @Override
-    public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
+    public void addChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
         if (pickToken instanceof Pick) {
             if (Pick.any.equals(pickToken))
                 throw new IllegalArgumentException("Choose step can not have an any-option as only one option per traverser is allowed");
             if (this.traversalPickOptions.containsKey(pickToken))
                 throw new IllegalArgumentException("Choose step can only have one traversal per pick token: " + pickToken);
         }
-        super.addGlobalChildOption(pickToken, traversalOption);
+        super.addChildOption(pickToken, traversalOption);
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
index de70479..1ce6137 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
@@ -18,8 +18,8 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
@@ -28,21 +28,21 @@ import java.util.Collections;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class UnionStep<S, E> extends BranchStep<S, E, TraversalOptionParent.Pick> {
+public final class UnionStep<S, E> extends BranchStep<S, E, Pick> {
 
     public UnionStep(final Traversal.Admin traversal, final Traversal.Admin<?, E>... unionTraversals) {
         super(traversal);
         this.setBranchTraversal(new ConstantTraversal<>(Pick.any));
         for (final Traversal.Admin<?, E> union : unionTraversals) {
-            this.addGlobalChildOption(Pick.any, (Traversal.Admin) union);
+            this.addChildOption(Pick.any, (Traversal.Admin) union);
         }
     }
 
     @Override
-    public void addGlobalChildOption(final Pick pickToken, final Traversal.Admin<S, E> traversalOption) {
+    public void addChildOption(final Pick pickToken, final Traversal.Admin<S, E> traversalOption) {
         if (Pick.any != pickToken)
             throw new IllegalArgumentException("Union step only supports the any token: " + pickToken);
-        super.addGlobalChildOption(pickToken, traversalOption);
+        super.addChildOption(pickToken, traversalOption);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
new file mode 100644
index 0000000..c726490
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
@@ -0,0 +1,340 @@
+/*
+ * 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.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.TraverserGenerator;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.IdentityTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.Attachable;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Implementation for the {@code mergeE()} step covering both the start step version and the one used mid-traversal.
+ */
+public class MergeEdgeStep<S> extends FlatMapStep<S, Edge> implements Mutating<Event>,
+        TraversalOptionParent<Merge, S, Edge> {
+
+    private final boolean isStart;
+    private boolean first = true;
+    private Traversal.Admin<S,Map<Object, Object>> searchCreateTraversal;
+    private Traversal.Admin<S, Map<Object, Object>> onCreateTraversal = null;
+    private Traversal.Admin<S, Map<String, Object>> onMatchTraversal = null;
+
+    protected CallbackRegistry<Event> callbackRegistry;
+
+    public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart) {
+        this(traversal, isStart, new IdentityTraversal<>());
+    }
+
+    public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, final Map<Object, Object> searchCreate) {
+        this(traversal, isStart, new ConstantTraversal<>(searchCreate));
+    }
+
+    public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin<?,Map<Object, Object>> searchCreateTraversal) {
+        super(traversal);
+        this.isStart = isStart;
+        this.searchCreateTraversal = integrateChild(searchCreateTraversal);
+    }
+
+    public Traversal.Admin<S, Map<Object, Object>> getSearchCreateTraversal() {
+        return searchCreateTraversal;
+    }
+
+    public Traversal.Admin<S, Map<Object, Object>> getOnCreateTraversal() {
+        return onCreateTraversal;
+    }
+
+    public Traversal.Admin<S, Map<String, Object>> getOnMatchTraversal() {
+        return onMatchTraversal;
+    }
+
+    /**
+     * Determines if this is a start step.
+     */
+    public boolean isStart() {
+        return isStart;
+    }
+
+    public boolean isFirst() {
+        return first;
+    }
+
+    public CallbackRegistry<Event> getCallbackRegistry() {
+        return callbackRegistry;
+    }
+
+    @Override
+    public void addChildOption(final Merge token, final Traversal.Admin<S, Edge> traversalOption) {
+        if (token == Merge.onCreate) {
+            this.onCreateTraversal = this.integrateChild(traversalOption);
+        } else if (token == Merge.onMatch) {
+            this.onMatchTraversal = this.integrateChild(traversalOption);
+        } else {
+            throw new UnsupportedOperationException(String.format("Option %s for Merge is not supported", token.name()));
+        }
+    }
+
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        final List<Traversal.Admin<S, E>> children = new ArrayList<>();
+        if (searchCreateTraversal != null) children.add((Traversal.Admin<S, E>) searchCreateTraversal);
+        if (onMatchTraversal != null) children.add((Traversal.Admin<S, E>) onMatchTraversal);
+        if (onCreateTraversal != null) children.add((Traversal.Admin<S, E>) onCreateTraversal);
+        return children;
+    }
+
+    @Override
+    public void configure(final Object... keyValues) {
+        // this is a Mutating step but property() should not be folded into this step. this exception should not
+        // end up visible to users really
+    }
+
+    @Override
+    public Parameters getParameters() {
+        // merge doesn't take fold ups of property() calls. those need to get treated as regular old PropertyStep
+        // instances. not sure if this should support with() though.....none of the other Mutating steps do.
+        return null;
+    }
+
+    @Override
+    protected Traverser.Admin<Edge> processNextStart() {
+        // when it's a start step a traverser needs to be created to kick off the traversal.
+        if (isStart && first) {
+            first = false;
+            final TraverserGenerator generator = this.getTraversal().getTraverserGenerator();
+            this.addStart(generator.generate(false, (Step) this, 1L));
+        }
+        return super.processNextStart();
+    }
+
+    /**
+     * Use the {@code Map} of search criteria to most efficiently return a {@code Stream<Edge>} of matching elements.
+     * Providers might override this method when extending this step to provide their own optimized mechanisms for
+     * matching the list of edges. This implementation is only optimized for the {@link T#id} so any other usage
+     * will simply be in-memory filtering which could be slow.
+     */
+    protected Stream<Edge> createSearchStream(final Map<Object,Object> search) {
+        final Graph graph = this.getTraversal().getGraph().get();
+
+        final Optional<Direction> directionUsedInLookup;
+        Stream<Edge> stream;
+        // prioritize lookup by id, then use vertices as starting point if possible
+        if (search.containsKey(T.id)) {
+            stream = IteratorUtils.stream(graph.edges(search.get(T.id)));
+            directionUsedInLookup = Optional.empty();
+        } else if (search.containsKey(Direction.OUT)) {
+            stream = IteratorUtils.stream(graph.vertices(search.get(Direction.OUT))).flatMap(v -> IteratorUtils.stream(v.edges(Direction.OUT)));
+            directionUsedInLookup = Optional.of(Direction.OUT);
+        } else if (search.containsKey(Direction.IN)) {
+            stream = IteratorUtils.stream(graph.vertices(search.get(Direction.IN))).flatMap(v -> IteratorUtils.stream(v.edges(Direction.IN)));
+            directionUsedInLookup = Optional.of(Direction.IN);
+        } else {
+            stream = IteratorUtils.stream(graph.edges());
+            directionUsedInLookup = Optional.empty();
+        }
+
+        // in-memory filter is not going to be nice here. it will be up to graphs to optimize this step as they do
+        // for other Mutation steps
+        stream = stream.filter(e -> {
+            // try to match on all search criteria skipping T.id as it was handled above
+            return search.entrySet().stream().filter(
+                    kv -> kv.getKey() != T.id && !(directionUsedInLookup.isPresent() && kv.getKey() == directionUsedInLookup.get())).
+                    allMatch(kv -> {
+                        if (kv.getKey() == T.label) {
+                            return e.label().equals(kv.getValue());
+                        } else if (kv.getKey() instanceof Direction) {
+                            final Direction direction = (Direction) kv.getKey();
+
+                            // try to take advantage of string id conversions of the graph by doing a lookup rather
+                            // than direct compare on id
+                            final Iterator<Vertex> found = graph.vertices(kv.getValue());
+                            return found.hasNext() && e.vertices(direction).next().equals(found.next());
+                        } else {
+                            final Property<Object> vp = e.property(kv.getKey().toString());
+                            return vp.isPresent() && kv.getValue().equals(vp.value());
+                        }
+                    });
+        });
+
+        return stream;
+    }
+
+    @Override
+    protected Iterator<Edge> flatMap(final Traverser.Admin<S> traverser) {
+        final S possibleVertex = traverser.get();
+        final Map<Object,Object> searchCreate = TraversalUtil.apply(traverser, searchCreateTraversal);
+
+        Vertex outV = (Vertex) searchCreate.getOrDefault(Direction.OUT, possibleVertex);
+        Vertex inV = (Vertex) searchCreate.getOrDefault(Direction.IN, possibleVertex);
+
+        if (inV instanceof Attachable)
+            inV = ((Attachable<Vertex>) inV)
+                    .attach(Attachable.Method.getOrCreate(this.getTraversal().getGraph().orElse(EmptyGraph.instance())));
+        if (outV instanceof Attachable)
+            outV = ((Attachable<Vertex>) outV)
+                    .attach(Attachable.Method.getOrCreate(this.getTraversal().getGraph().orElse(EmptyGraph.instance())));
+
+        final Vertex fromV = outV;
+        final Vertex toV = inV;
+
+        Stream<Edge> stream = createSearchStream(searchCreate);
+        stream = stream.map(e -> {
+            // if no onMatch is defined then there is no update - return the edge unchanged
+            if (null == onMatchTraversal) return e;
+
+            // assume good input from GraphTraversal - folks might drop in a T here even though it is immutable
+            final Map<String, Object> onMatchMap = TraversalUtil.apply(traverser, onMatchTraversal);
+            onMatchMap.forEach((key, value) -> {
+                // trigger callbacks for eventing - in this case, it's a EdgePropertyChangedEvent. if there's no
+                // registry/callbacks then just set the property
+                if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+                    final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+                    final Property<?> p = e.property(key);
+                    final Property<Object> oldValue = p.isPresent() ? eventStrategy.detach(e.property(key)) : null;
+                    final Event.EdgePropertyChangedEvent vpce = new Event.EdgePropertyChangedEvent(eventStrategy.detach(e), oldValue, value);
+                    this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce));
+                }
+                e.property(key, value);
+            });
+
+            return e;
+        });
+
+        // if the stream has something then there is a match (possibly updated) and is returned, otherwise a new
+        // edge is created
+        final Iterator<Edge> edges = stream.iterator();
+        if (edges.hasNext()) {
+            return edges;
+        } else {
+            final Edge edge;
+
+            // if there is an onCreateTraversal then the search criteria is ignored for the creation as it is provided
+            // by way of the traversal which will return the Map
+            final Map<Object,Object> m = null == onCreateTraversal ? searchCreate : TraversalUtil.apply(traverser, onCreateTraversal);
+            final List<Object> keyValues = new ArrayList<>();
+            String label = Edge.DEFAULT_LABEL;
+            for (Map.Entry<Object, Object> entry : m.entrySet()) {
+                // skip Direction keys as they are already handled in getting fromV/toV
+                if (entry.getKey() instanceof Direction) continue;
+
+                if (entry.getKey().equals(T.label)) {
+                    label = (String) entry.getValue();
+                } else {
+                    keyValues.add(entry.getKey());
+                    keyValues.add(entry.getValue());
+                }
+            }
+
+            edge = fromV.addEdge(label, toV, keyValues.toArray(new Object[keyValues.size()]));
+
+            // trigger callbacks for eventing - in this case, it's a VertexAddedEvent
+            if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+                final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+                final Event.EdgeAddedEvent vae = new Event.EdgeAddedEvent(eventStrategy.detach(edge));
+                this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vae));
+            }
+
+            return IteratorUtils.of(edge);
+        }
+    }
+
+    @Override
+    public CallbackRegistry<Event> getMutatingCallbackRegistry() {
+        if (null == callbackRegistry) callbackRegistry = new ListCallbackRegistry<>();
+        return callbackRegistry;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        if (searchCreateTraversal != null)
+            result ^= searchCreateTraversal.hashCode();
+        if (onCreateTraversal != null)
+            result ^= onCreateTraversal.hashCode();
+        if (onMatchTraversal != null)
+            result ^= onMatchTraversal.hashCode();
+        return result;
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        first = true;
+        searchCreateTraversal.reset();
+        if (onCreateTraversal != null) onCreateTraversal.reset();
+        if (onMatchTraversal != null) onMatchTraversal.reset();
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return this.getSelfAndChildRequirements();
+    }
+
+    @Override
+    public String toString() {
+        return StringFactory.stepString(this, searchCreateTraversal, onCreateTraversal, onMatchTraversal);
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        this.integrateChild(searchCreateTraversal);
+        this.integrateChild(onCreateTraversal);
+        this.integrateChild(onMatchTraversal);
+    }
+
+    @Override
+    public MergeEdgeStep<S> clone() {
+        final MergeEdgeStep<S> clone = (MergeEdgeStep<S>) super.clone();
+        clone.searchCreateTraversal = searchCreateTraversal.clone();
+        clone.onCreateTraversal = onCreateTraversal != null ? onCreateTraversal.clone() : null;
+        clone.onMatchTraversal = onMatchTraversal != null ? onMatchTraversal.clone() : null;
+        return clone;
+    }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java
new file mode 100644
index 0000000..c32a18c
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java
@@ -0,0 +1,293 @@
+/*
+ * 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.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.TraverserGenerator;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.IdentityTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Implementation for the {@code mergeV()} step covering both the start step version and the one used mid-traversal.
+ */
+public class MergeVertexStep<S> extends FlatMapStep<S, Vertex> implements Mutating<Event>,
+        TraversalOptionParent<Merge, S, Vertex> {
+
+    private final boolean isStart;
+    private boolean first = true;
+    private Traversal.Admin<S,Map<Object, Object>> searchCreateTraversal;
+    private Traversal.Admin<S, Map<Object, Object>> onCreateTraversal = null;
+    private Traversal.Admin<S, Map<String, Object>> onMatchTraversal = null;
+
+    protected CallbackRegistry<Event> callbackRegistry;
+
+    public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart) {
+        this(traversal, isStart, new IdentityTraversal());
+    }
+
+    public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final Map<Object, Object> searchCreate) {
+        this(traversal, isStart, new ConstantTraversal<>(searchCreate));
+    }
+
+    public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin<S,Map<Object, Object>> searchCreateTraversal) {
+        super(traversal);
+        this.isStart = isStart;
+        this.searchCreateTraversal = integrateChild(searchCreateTraversal);
+    }
+
+    public Traversal.Admin<S, Map<Object, Object>> getSearchCreateTraversal() {
+        return searchCreateTraversal;
+    }
+
+    public Traversal.Admin<S, Map<Object, Object>> getOnCreateTraversal() {
+        return onCreateTraversal;
+    }
+
+    public Traversal.Admin<S, Map<String, Object>> getOnMatchTraversal() {
+        return onMatchTraversal;
+    }
+
+    /**
+     * Determines if this is a start step.
+     */
+    public boolean isStart() {
+        return isStart;
+    }
+
+    public boolean isFirst() {
+        return first;
+    }
+
+    public CallbackRegistry<Event> getCallbackRegistry() {
+        return callbackRegistry;
+    }
+
+    @Override
+    public void addChildOption(final Merge token, final Traversal.Admin<S, Vertex> traversalOption) {
+        if (token == Merge.onCreate) {
+            this.onCreateTraversal = this.integrateChild(traversalOption);
+        } else if (token == Merge.onMatch) {
+            this.onMatchTraversal = this.integrateChild(traversalOption);
+        } else {
+            throw new UnsupportedOperationException(String.format("Option %s for Merge is not supported", token.name()));
+        }
+    }
+
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        final List<Traversal.Admin<S, E>> children = new ArrayList<>();
+        if (searchCreateTraversal != null) children.add((Traversal.Admin<S, E>) searchCreateTraversal);
+        if (onMatchTraversal != null) children.add((Traversal.Admin<S, E>) onMatchTraversal);
+        if (onCreateTraversal != null) children.add((Traversal.Admin<S, E>) onCreateTraversal);
+        return children;
+    }
+
+    @Override
+    public void configure(final Object... keyValues) {
+        // this is a Mutating step but property() should not be folded into this step. this exception should not
+        // end up visible to users really
+    }
+
+    @Override
+    public Parameters getParameters() {
+        // merge doesn't take fold ups of property() calls. those need to get treated as regular old PropertyStep
+        // instances. not sure if this should support with() though.....none of the other Mutating steps do.
+        return null;
+    }
+
+    @Override
+    protected Traverser.Admin<Vertex> processNextStart() {
+        // when it's a start step a traverser needs to be created to kick off the traversal.
+        if (isStart && first) {
+            first = false;
+            final TraverserGenerator generator = this.getTraversal().getTraverserGenerator();
+            this.addStart(generator.generate(false, (Step) this, 1L));
+        }
+        return super.processNextStart();
+    }
+
+    /**
+     * Use the {@code Map} of search criteria to most efficiently return a {@code Stream<Vertex>} of matching elements.
+     * Providers might override this method when extending this step to provide their own optimized mechanisms for
+     * matching the list of vertices. This implementation is only optimized for the {@link T#id} so any other usage
+     * will simply be in-memory filtering which could be slow.
+     */
+    protected Stream<Vertex> createSearchStream(final Map<Object,Object> search) {
+        final Graph graph = this.getTraversal().getGraph().get();
+
+        Stream<Vertex> stream;
+        // prioritize lookup by id
+        if (search.containsKey(T.id))
+            stream = IteratorUtils.stream(graph.vertices(search.get(T.id)));
+        else
+            stream = IteratorUtils.stream(graph.vertices());
+
+        // in-memory filter is not going to be nice here. it will be up to graphs to optimize this step as they do
+        // for other Mutation steps
+        stream = stream.filter(v -> {
+            // try to match on all search criteria skipping T.id as it was handled above
+            return search.entrySet().stream().filter(kv -> kv.getKey() != T.id).allMatch(kv -> {
+                if (kv.getKey() == T.label) {
+                    return v.label().equals(kv.getValue());
+                } else {
+                    final VertexProperty<Object> vp = v.property(kv.getKey().toString());
+                    return vp.isPresent() && kv.getValue().equals(vp.value());
+                }
+            });
+        });
+
+        return stream;
+    }
+
+    @Override
+    protected Iterator<Vertex> flatMap(final Traverser.Admin<S> traverser) {
+        final Map<Object,Object> searchCreate = TraversalUtil.apply(traverser, searchCreateTraversal);
+
+        Stream<Vertex> stream = createSearchStream(searchCreate);
+        stream = stream.map(v -> {
+            // if no onMatch is defined then there is no update - return the vertex unchanged
+            if (null == onMatchTraversal) return v;
+
+            // assume good input from GraphTraversal - folks might drop in a T here even though it is immutable
+            final Map<String, Object> onMatchMap = TraversalUtil.apply(traverser, onMatchTraversal);
+            onMatchMap.forEach((key, value) -> {
+                // trigger callbacks for eventing - in this case, it's a VertexPropertyChangedEvent. if there's no
+                // registry/callbacks then just set the property
+                if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+                    final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+                    final Property<?> p = v.property(key);
+                    final Property<Object> oldValue = p.isPresent() ? eventStrategy.detach(v.property(key)) : null;
+                    final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, value);
+                    this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce));
+                }
+                v.property(key, value);
+            });
+
+            return v;
+        });
+
+        // if the stream has something then there is a match (possibly updated) and is returned, otherwise a new
+        // vertex is created
+        final Iterator<Vertex> vertices = stream.iterator();
+        if (vertices.hasNext()) {
+            return vertices;
+        } else {
+            final Vertex vertex;
+
+            // if there is an onCreateTraversal then the search criteria is ignored for the creation as it is provided
+            // by way of the traversal which will return the Map
+            final Map<Object,Object> m = null == onCreateTraversal ? searchCreate : TraversalUtil.apply(traverser, onCreateTraversal);
+            final List<Object> keyValues = new ArrayList<>();
+            for (Map.Entry<Object, Object> entry : m.entrySet()) {
+                keyValues.add(entry.getKey());
+                keyValues.add(entry.getValue());
+            }
+            vertex = this.getTraversal().getGraph().get().addVertex(keyValues.toArray(new Object[keyValues.size()]));
+
+            // trigger callbacks for eventing - in this case, it's a VertexAddedEvent
+            if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+                final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+                final Event.VertexAddedEvent vae = new Event.VertexAddedEvent(eventStrategy.detach(vertex));
+                this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vae));
+            }
+
+            return IteratorUtils.of(vertex);
+        }
+    }
+
+    @Override
+    public CallbackRegistry<Event> getMutatingCallbackRegistry() {
+        if (null == callbackRegistry) callbackRegistry = new ListCallbackRegistry<>();
+        return callbackRegistry;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        if (searchCreateTraversal != null)
+            result ^= searchCreateTraversal.hashCode();
+        if (onCreateTraversal != null)
+            result ^= onCreateTraversal.hashCode();
+        if (onMatchTraversal != null)
+            result ^= onMatchTraversal.hashCode();
+        return result;
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        first = true;
+        searchCreateTraversal.reset();
+        if (onCreateTraversal != null) onCreateTraversal.reset();
+        if (onMatchTraversal != null) onMatchTraversal.reset();
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return this.getSelfAndChildRequirements();
+    }
+
+    @Override
+    public String toString() {
+        return StringFactory.stepString(this, searchCreateTraversal, onCreateTraversal, onMatchTraversal);
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        this.integrateChild(searchCreateTraversal);
+        this.integrateChild(onCreateTraversal);
+        this.integrateChild(onMatchTraversal);
+    }
+
+    @Override
+    public MergeVertexStep<S> clone() {
+        final MergeVertexStep<S> clone = (MergeVertexStep<S>) super.clone();
+        clone.searchCreateTraversal = searchCreateTraversal.clone();
+        clone.onCreateTraversal = onCreateTraversal != null ? onCreateTraversal.clone() : null;
+        clone.onMatchTraversal = onMatchTraversal != null ? onMatchTraversal.clone() : null;
+        return clone;
+    }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java
index 732c30b..fa34dfa 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java
@@ -129,7 +129,8 @@ public interface Event {
     }
 
     /**
-     * Represents an action where a {@link VertexProperty} is modified on a {@link Vertex}.
+     * Represents an action where a {@link VertexProperty} is modified on a {@link Vertex}. If the {@link Property} is
+     * new then the {@code oldValue} will be {@code null}.
      */
     class VertexPropertyChangedEvent extends ElementPropertyChangedEvent {
 
@@ -144,7 +145,8 @@ public interface Event {
     }
 
     /**
-     * Represents an action where a {@link Property} is modified on a {@link VertexProperty}.
+     * Represents an action where a {@link Property} is modified on a {@link VertexProperty}. If the {@link Property} is
+     * new then the {@code oldValue} will be {@code null}.
      */
     class VertexPropertyPropertyChangedEvent extends ElementPropertyChangedEvent {
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
index 4f375e5..6bd3442 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
@@ -19,18 +19,19 @@
 
 package org.apache.tinkerpop.gremlin.process.traversal.translator;
 
-import org.apache.commons.configuration2.ConfigurationConverter;
 import org.apache.commons.text.StringEscapeUtils;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Script;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -171,7 +172,7 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
         }
 
         @Override
-        protected String getSyntax(final TraversalOptionParent.Pick o) {
+        protected String getSyntax(final Pick o) {
             return "Pick." + SymbolHelper.toCSharp(o.toString());
         }
 
@@ -346,6 +347,33 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
                         script.append(", (").append(castSecondArgTo).append(") ");
                         convertToScript(instruction.getArguments()[1]);
                         script.append(",");
+                    } else if (methodName.equals(GraphTraversal.Symbols.mergeE) || methodName.equals(GraphTraversal.Symbols.mergeV)) {
+                        // there must be at least one argument - if null go with Map
+                        final Object instArg = instruction.getArguments()[0];
+                        if (null == instArg) {
+                            script.append("(IDictionary<object,object>) null");
+                        } else {
+                            if (instArg instanceof Traversal) {
+                                script.append("(ITraversal) ");
+                            } else {
+                                script.append("(IDictionary<object,object>) ");
+                            }
+                            convertToScript(instArg);
+                            script.append(")");
+                        }
+                    } else if (methodName.equals(GraphTraversal.Symbols.option) &&
+                            instruction.getArguments().length == 2 && instruction.getArguments()[0] instanceof Merge) {
+                        final Object[] instArgs = instruction.getArguments();
+                        // trying to catch option(Merge,Traversal|Map)
+                        convertToScript(instArgs[0]);
+                        script.append(", ");
+                        if (instArgs[1] instanceof Traversal) {
+                            script.append("(ITraversal) ");
+                        } else {
+                            script.append("(IDictionary<object,object>) ");
+                        }
+                        convertToScript(instArgs[1]);
+                        script.append(")");
                     } else {
                         final Object[] instArgs = instruction.getArguments();
                         for (int idx = 0; idx < instArgs.length; idx++) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
index ef544b4..0143c27 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
@@ -24,14 +24,12 @@ import org.apache.commons.text.StringEscapeUtils;
 import org.apache.tinkerpop.gremlin.jsr223.CoreImports;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Script;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -175,8 +173,8 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
         }
 
         @Override
-        protected String getSyntax(final TraversalOptionParent.Pick o) {
-            return "TraversalOptionParent.Pick." + o.toString();
+        protected String getSyntax(final Pick o) {
+            return "Pick." + o.toString();
         }
 
         @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java
index e9cf748..f95d288 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java
@@ -23,13 +23,13 @@ import org.apache.commons.configuration2.ConfigurationConverter;
 import org.apache.commons.text.StringEscapeUtils;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Script;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -167,7 +167,7 @@ public final class JavascriptTranslator implements Translator.ScriptTranslator {
         }
 
         @Override
-        protected String getSyntax(final TraversalOptionParent.Pick o) {
+        protected String getSyntax(final Pick o) {
             return "Pick." + o.toString();
         }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java
index 09e4447..987fa3e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java
@@ -23,6 +23,7 @@ import org.apache.commons.configuration2.ConfigurationConverter;
 import org.apache.commons.text.StringEscapeUtils;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Script;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
@@ -30,7 +31,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -188,7 +188,7 @@ public final class PythonTranslator implements Translator.ScriptTranslator {
         }
 
         @Override
-        protected String getSyntax(final TraversalOptionParent.Pick o) {
+        protected String getSyntax(final Pick o) {
             return "Pick." + resolveSymbol(o.toString());
         }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java
index 0159d4f..75b8060 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java
@@ -70,6 +70,7 @@ public enum DataType {
     TREE(0X2B),
     METRICS(0x2C),
     TRAVERSALMETRICS(0x2D),
+    MERGE(0x2E),
 
     CHAR(0X80),
     DURATION(0X81),
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
index ced0088..4edcad2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
@@ -18,6 +18,8 @@
  */
 package org.apache.tinkerpop.gremlin.structure.io.binary;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.structure.io.binary.types.*;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
@@ -30,7 +32,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
 import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
@@ -117,9 +118,10 @@ public class TypeSerializerRegistry {
             new RegistryEntry<>(VertexProperty.Cardinality.class, EnumSerializer.CardinalitySerializer),
             new RegistryEntry<>(Column.class, EnumSerializer.ColumnSerializer),
             new RegistryEntry<>(Direction.class, EnumSerializer.DirectionSerializer),
+            new RegistryEntry<>(Merge.class, EnumSerializer.MergeSerializer),
             new RegistryEntry<>(Operator.class, EnumSerializer.OperatorSerializer),
             new RegistryEntry<>(Order.class, EnumSerializer.OrderSerializer),
-            new RegistryEntry<>(TraversalOptionParent.Pick.class, EnumSerializer.PickSerializer),
+            new RegistryEntry<>(Pick.class, EnumSerializer.PickSerializer),
             new RegistryEntry<>(Pop.class, EnumSerializer.PopSerializer),
             new RegistryEntry<>(Lambda.class, new LambdaSerializer()),
             new RegistryEntry<>(P.class, new PSerializer<>(DataType.P, P.class)),
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java
index 12e2d15..41cf7fc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java
@@ -18,6 +18,8 @@
  */
 package org.apache.tinkerpop.gremlin.structure.io.binary.types;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.structure.io.binary.DataType;
 import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
 import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
@@ -26,7 +28,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.structure.Column;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
@@ -47,9 +48,10 @@ public class EnumSerializer<E extends Enum> extends SimpleTypeSerializer<E> {
     public static final EnumSerializer<VertexProperty.Cardinality> CardinalitySerializer = new EnumSerializer<>(DataType.CARDINALITY, VertexProperty.Cardinality::valueOf);
     public static final EnumSerializer<Column> ColumnSerializer = new EnumSerializer<>(DataType.COLUMN, Column::valueOf);
     public static final EnumSerializer<Direction> DirectionSerializer = new EnumSerializer<>(DataType.DIRECTION, Direction::valueOf);
+    public static final EnumSerializer<Merge> MergeSerializer = new EnumSerializer<>(DataType.MERGE, Merge::valueOf);
     public static final EnumSerializer<Operator> OperatorSerializer = new EnumSerializer<>(DataType.OPERATOR, Operator::valueOf);
     public static final EnumSerializer<Order> OrderSerializer = new EnumSerializer<>(DataType.ORDER, Order::valueOf);
-    public static final EnumSerializer<TraversalOptionParent.Pick> PickSerializer = new EnumSerializer<>(DataType.PICK, TraversalOptionParent.Pick::valueOf);
+    public static final EnumSerializer<Pick> PickSerializer = new EnumSerializer<>(DataType.PICK, Pick::valueOf);
     public static final EnumSerializer<Pop> PopSerializer = new EnumSerializer<>(DataType.POP, Pop::valueOf);
     public static final EnumSerializer<Scope> ScopeSerializer = new EnumSerializer<>(DataType.SCOPE, Scope::valueOf);
     public static final EnumSerializer<T> TSerializer = new EnumSerializer<>(DataType.T, T::valueOf);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index eda0d37..82d818f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
@@ -21,10 +21,12 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
 import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy;
 import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization.GraphFilterStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
@@ -32,7 +34,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ConnectiveStrategy;
@@ -174,11 +175,12 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             VertexProperty.Cardinality.class,
                             Column.class,
                             Direction.class,
+                            Merge.class,
                             Operator.class,
                             Order.class,
                             Pop.class,
                             SackFunctions.Barrier.class,
-                            TraversalOptionParent.Pick.class,
+                            Pick.class,
                             Scope.class,
                             T.class).forEach(e -> put(e, e.getSimpleName()));
                     Arrays.asList(
@@ -257,12 +259,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.class,
                     Column.class,
                     Direction.class,
+                    Merge.class,
                     Operator.class,
                     Order.class,
                     Pop.class,
                     SackFunctions.Barrier.class,
                     Scope.class,
-                    TraversalOptionParent.Pick.class,
+                    Pick.class,
                     T.class).forEach(e -> addSerializer(e, new TraversalSerializersV3d0.EnumJacksonSerializer()));
             addSerializer(P.class, new TraversalSerializersV3d0.PJacksonSerializer());
             addSerializer(Lambda.class, new TraversalSerializersV3d0.LambdaJacksonSerializer());
@@ -299,12 +302,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.values(),
                     Column.values(),
                     Direction.values(),
+                    Merge.values(),
                     Operator.values(),
                     Order.values(),
                     Pop.values(),
                     SackFunctions.Barrier.values(),
                     Scope.values(),
-                    TraversalOptionParent.Pick.values(),
+                    Pick.values(),
                     T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV3d0.EnumJacksonDeserializer(e.getDeclaringClass())));
             addDeserializer(P.class, new TraversalSerializersV3d0.PJacksonDeserializer());
             addDeserializer(TextP.class, new TraversalSerializersV3d0.TextPJacksonDeserializer());
@@ -412,11 +416,12 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             VertexProperty.Cardinality.class,
                             Column.class,
                             Direction.class,
+                            Merge.class,
                             Operator.class,
                             Order.class,
                             Pop.class,
                             SackFunctions.Barrier.class,
-                            TraversalOptionParent.Pick.class,
+                            Pick.class,
                             Scope.class,
                             T.class).forEach(e -> put(e, e.getSimpleName()));
                     Arrays.asList(
@@ -491,12 +496,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.class,
                     Column.class,
                     Direction.class,
+                    Merge.class,
                     Operator.class,
                     Order.class,
                     Pop.class,
                     SackFunctions.Barrier.class,
                     Scope.class,
-                    TraversalOptionParent.Pick.class,
+                    Pick.class,
                     T.class).forEach(e -> addSerializer(e, new TraversalSerializersV2d0.EnumJacksonSerializer()));
             addSerializer(P.class, new TraversalSerializersV2d0.PJacksonSerializer());
             addSerializer(Lambda.class, new TraversalSerializersV2d0.LambdaJacksonSerializer());
@@ -527,12 +533,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.values(),
                     Column.values(),
                     Direction.values(),
+                    Merge.values(),
                     Operator.values(),
                     Order.values(),
                     Pop.values(),
                     SackFunctions.Barrier.values(),
                     Scope.values(),
-                    TraversalOptionParent.Pick.values(),
+                    Pick.values(),
                     T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV2d0.EnumJacksonDeserializer(e.getDeclaringClass())));
             addDeserializer(P.class, new TraversalSerializersV2d0.PJacksonDeserializer());
             addDeserializer(TextP.class, new TraversalSerializersV2d0.TextPJacksonDeserializer());
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java
index 3fdb50a..e50037c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java
@@ -21,11 +21,11 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics;
 import org.apache.tinkerpop.gremlin.structure.Column;
@@ -131,8 +131,8 @@ public class GraphSONTypeSerializerV2d0 extends AbstractGraphSONTypeSerializer {
             mapped = Pop.class;
         else if (SackFunctions.Barrier.class.isAssignableFrom(c))
             mapped = SackFunctions.Barrier.class;
-        else if (TraversalOptionParent.Pick.class.isAssignableFrom(c))
-            mapped = TraversalOptionParent.Pick.class;
+        else if (Pick.class.isAssignableFrom(c))
+            mapped = Pick.class;
         else if (Scope.class.isAssignableFrom(c))
             mapped = Scope.class;
         else if (T.class.isAssignableFrom(c))
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java
index eca91ef..4349e47 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java
@@ -21,11 +21,11 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
 import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
@@ -165,8 +165,8 @@ public class GraphSONTypeSerializerV3d0 extends AbstractGraphSONTypeSerializer {
             mapped = Pop.class;
         else if (SackFunctions.Barrier.class.isAssignableFrom(c))
             mapped = SackFunctions.Barrier.class;
-        else if (TraversalOptionParent.Pick.class.isAssignableFrom(c))
-            mapped = TraversalOptionParent.Pick.class;
+        else if (Pick.class.isAssignableFrom(c))
+            mapped = Pick.class;
         else if (Scope.class.isAssignableFrom(c))
             mapped = Scope.class;
         else if (T.class.isAssignableFrom(c))
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
index bd2b1b0..a5774bb 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
@@ -26,15 +26,16 @@ import org.apache.tinkerpop.gremlin.process.computer.util.MapMemory;
 import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.Contains;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GroupCountStep;
@@ -329,7 +330,8 @@ public enum GryoVersion {
             add(GryoTypeReg.of(Column.class, 132));
             add(GryoTypeReg.of(Pop.class, 133));
             add(GryoTypeReg.of(SackFunctions.Barrier.class, 135));
-            add(GryoTypeReg.of(TraversalOptionParent.Pick.class, 137));
+            add(GryoTypeReg.of(Pick.class, 137));
+            add(GryoTypeReg.of(Merge.class, 196));              // ***LAST ID***
             add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer()));
             add(GryoTypeReg.of(MultiComparator.class, 165));
 
@@ -343,7 +345,7 @@ public enum GryoVersion {
             add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 144));
             add(GryoTypeReg.of(AdjacentToIncidentStrategy.class, 145));
             add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191));
-            add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer())); // ***LAST ID***
+            add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer()));
             add(GryoTypeReg.of(CountStrategy.class, 155));
             add(GryoTypeReg.of(FilterRankingStrategy.class, 146));
             add(GryoTypeReg.of(IdentityRemovalStrategy.class, 147));
@@ -529,7 +531,8 @@ public enum GryoVersion {
             add(GryoTypeReg.of(Column.class, 132));
             add(GryoTypeReg.of(Pop.class, 133));
             add(GryoTypeReg.of(SackFunctions.Barrier.class, 135));
-            add(GryoTypeReg.of(TraversalOptionParent.Pick.class, 137));
+            add(GryoTypeReg.of(Pick.class, 137));
+            add(GryoTypeReg.of(Merge.class, 196));       // ***LAST ID***
             add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer()));
             add(GryoTypeReg.of(MultiComparator.class, 165));
 
@@ -586,7 +589,7 @@ public enum GryoVersion {
             add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 144));
             add(GryoTypeReg.of(AdjacentToIncidentStrategy.class, 145));
             add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191));
-            add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer())); // ***LAST ID***
+            add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer()));
             add(GryoTypeReg.of(CountStrategy.class, 155));
             add(GryoTypeReg.of(FilterRankingStrategy.class, 146));
             add(GryoTypeReg.of(IdentityRemovalStrategy.class, 147));
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java
index 0d490df..6ed0d3f 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java
@@ -53,13 +53,14 @@ public class TraversalEnumParserTest {
                     {"org.apache.tinkerpop.gremlin.process.traversal.Scope", "traversalScope"},
                     {"org.apache.tinkerpop.gremlin.process.traversal.Order", "traversalOrder"},
                     {"org.apache.tinkerpop.gremlin.process.traversal.Pop", "traversalPop"},
+                    {"org.apache.tinkerpop.gremlin.process.traversal.Pick", "traversalOptionParent"},
+                    {"org.apache.tinkerpop.gremlin.process.traversal.Merge", "traversalMerge"},
                     {"org.apache.tinkerpop.gremlin.process.traversal.SackFunctions$Barrier", "traversalSackMethod"},
                     {"org.apache.tinkerpop.gremlin.process.traversal.Operator", "traversalOperator"},
                     {"org.apache.tinkerpop.gremlin.structure.T", "traversalToken"},
                     {"org.apache.tinkerpop.gremlin.structure.Column", "traversalColumn"},
                     {"org.apache.tinkerpop.gremlin.structure.Direction", "traversalDirection"},
                     {"org.apache.tinkerpop.gremlin.structure.VertexProperty$Cardinality", "traversalCardinality"},
-                    {"org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent$Pick", "traversalOptionParent"},
             });
         }
 
@@ -91,6 +92,7 @@ public class TraversalEnumParserTest {
             return Arrays.asList(new Object[][]{
                     {"org.apache.tinkerpop.gremlin.process.traversal.Scope", "traversalScope"},
                     {"org.apache.tinkerpop.gremlin.process.traversal.Order", "traversalOrder"},
+                    {"org.apache.tinkerpop.gremlin.process.traversal.Merge", "traversalMerge"},
                     {"org.apache.tinkerpop.gremlin.structure.T", "traversalToken"}
             });
         }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java
index f68af08..629ea72 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java
@@ -28,7 +28,7 @@ import java.util.List;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.none;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.none;
 
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java
index 885da77..604ae20 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java
@@ -29,7 +29,7 @@ import java.util.List;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.none;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.none;
 
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java
index fcb040a..656d542 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.structure.io.gryo;
 
 import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.io.IoX;
@@ -377,6 +378,12 @@ public class GryoMapperTest {
         assertThat(Arrays.equals(bb.array(), serializeDeserialize(bb, ByteBuffer.class).array()), is(true));
     }
 
+    @Test
+    public void shouldHandleMerge() throws Exception {
+        final Merge merge = Merge.onCreate;
+        assertEquals(merge, serializeDeserialize(merge, Merge.class));
+    }
+
     public <T> T serializeDeserialize(final Object o, final Class<T> clazz) throws Exception {
         final Kryo kryo = builder.get().create().createMapper();
         try (final ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
diff --git a/gremlin-dotnet/build/generate.groovy b/gremlin-dotnet/build/generate.groovy
index 0d6e8ba..fedf0dd 100644
--- a/gremlin-dotnet/build/generate.groovy
+++ b/gremlin-dotnet/build/generate.groovy
@@ -31,6 +31,16 @@ import java.nio.file.Paths
 
 import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
 
+// getting an exception like:
+// > InvocationTargetException: javax.script.ScriptException: groovy.lang.MissingMethodException: No signature of
+// > method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.mergeE() is applicable for
+// > argument types: (String) values: [4ffdea36-4a0e-4681-acba-e76875d1b25b]
+// usually means bindings are not being extracted properly by VarAsBindingASTTransformation which typically happens
+// when a step is taking an argument that cannot properly resolve to the type required by the step itself. there are
+// special cases in that VarAsBindingASTTransformation class which might need to be adjusted. Editing the
+// GremlinGroovyScriptEngineTest#shouldProduceBindingsForVars() with the failing step and argument can typically make
+// this issue relatively easy to debug and enforce.
+
 // file is overwritten on each generation
 radishGremlinFile = new File("${projectBaseDir}/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs")
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index ebd1cdf..03521f0 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1090,6 +1090,61 @@ namespace Gremlin.Net.Process.Traversal
         }
 
         /// <summary>
+        ///     Adds the mergeE step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, Edge> MergeE ()
+        {
+            Bytecode.AddStep("mergeE");
+            return Wrap<S, Edge>(this);
+        }
+
+        /// <summary>
+        ///     Adds the mergeE step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, Edge> MergeE (IDictionary<object,object> m)
+        {
+            Bytecode.AddStep("mergeE", m);
+            return Wrap<S, Edge>(this);
+        }
+
+        /// <summary>
+        ///     Adds the mergeE step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, Edge> MergeE (ITraversal t)
+        {
+            Bytecode.AddStep("mergeE", t);
+            return Wrap<S, Edge>(this);
+        }
+
+        /// <summary>
+        ///     Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, Vertex> MergeV ()
+        {
+            Bytecode.AddStep("mergeV");
+            return Wrap<S, Vertex>(this);
+        }
+
+        /// <summary>
+        ///     Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, Vertex> MergeV (IDictionary<object,object> m)
+        {
+            Bytecode.AddStep("mergeV", m);
+            return Wrap<S, Vertex>(this);
+        }
+
+        /// <summary>
+        ///     Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, Vertex> MergeV (ITraversal t)
+        {
+            Bytecode.AddStep("mergeV", t);
+            return Wrap<S, Vertex>(this);
+        }
+
+
+        /// <summary>
         ///     Adds the min step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
         public GraphTraversal<S, E2> Min<E2> ()
@@ -1137,6 +1192,15 @@ namespace Gremlin.Net.Process.Traversal
         /// <summary>
         ///     Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
+        public GraphTraversal<S, E> Option (object pickToken, IDictionary<object,object> traversalOption)
+        {
+            Bytecode.AddStep("option", pickToken, traversalOption);
+            return Wrap<S, E>(this);
+        }
+
+        /// <summary>
+        ///     Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
         public GraphTraversal<S, E> Option (ITraversal traversalOption)
         {
             Bytecode.AddStep("option", traversalOption);
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
index ae5b1d9..5ad2f7d 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
@@ -336,7 +336,7 @@ namespace Gremlin.Net.Process.Traversal
         public GraphTraversal<Edge, Edge> AddE(string label)
         {
             var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
-                traversal.Bytecode.AddStep("addE", label);
+            traversal.Bytecode.AddStep("addE", label);
             return traversal;
         }
 
@@ -347,7 +347,29 @@ namespace Gremlin.Net.Process.Traversal
         public GraphTraversal<Edge, Edge> AddE(ITraversal edgeLabelTraversal)
         {
             var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
-                traversal.Bytecode.AddStep("addE", edgeLabelTraversal);
+            traversal.Bytecode.AddStep("addE", edgeLabelTraversal);
+            return traversal;
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeE step to that
+        ///     traversal.
+        /// </summary>
+        public GraphTraversal<Edge, Edge> MergeE(IDictionary<object,object> m)
+        {
+            var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
+            traversal.Bytecode.AddStep("mergeE", m);
+            return traversal;
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeE step to that
+        ///     traversal.
+        /// </summary>
+        public GraphTraversal<Edge, Edge> MergeE(ITraversal t)
+        {
+            var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
+            traversal.Bytecode.AddStep("mergeE", t);
             return traversal;
         }
 
@@ -358,7 +380,7 @@ namespace Gremlin.Net.Process.Traversal
         public GraphTraversal<Vertex, Vertex> AddV()
         {
             var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
-                traversal.Bytecode.AddStep("addV");
+            traversal.Bytecode.AddStep("addV");
             return traversal;
         }
 
@@ -369,7 +391,7 @@ namespace Gremlin.Net.Process.Traversal
         public GraphTraversal<Vertex, Vertex> AddV(string label)
         {
             var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
-                traversal.Bytecode.AddStep("addV", label);
+            traversal.Bytecode.AddStep("addV", label);
             return traversal;
         }
 
@@ -380,7 +402,29 @@ namespace Gremlin.Net.Process.Traversal
         public GraphTraversal<Vertex, Vertex> AddV(ITraversal vertexLabelTraversal)
         {
             var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
-                traversal.Bytecode.AddStep("addV", vertexLabelTraversal);
+            traversal.Bytecode.AddStep("addV", vertexLabelTraversal);
+            return traversal;
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeV step to that
+        ///     traversal.
+        /// </summary>
+        public GraphTraversal<Vertex, Vertex> MergeV(IDictionary<object,object> m)
+        {
+            var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
+            traversal.Bytecode.AddStep("mergeV", m);
+            return traversal;
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeV step to that
+        ///     traversal.
+        /// </summary>
+        public GraphTraversal<Vertex, Vertex> MergeV(ITraversal t)
+        {
+            var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
+            traversal.Bytecode.AddStep("mergeV", t);
             return traversal;
         }
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Merge.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Merge.cs
new file mode 100644
index 0000000..b62fc8f
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Merge.cs
@@ -0,0 +1,63 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace Gremlin.Net.Process.Traversal
+{
+#pragma warning disable 1591
+
+    public class Merge : EnumWrapper, IFunction
+    {
+        private Merge(string enumValue)
+            : base("Merge", enumValue)
+        {
+        }
+
+        public static Merge OnCreate => new Merge("onCreate");
+
+        public static Merge OnMatch => new Merge("onMatch");
+
+        private static readonly IDictionary<string, Merge> Properties = new Dictionary<string, Merge>
+        {
+            { "onCreate", OnCreate },
+            { "onMatch", OnMatch },
+        };
+
+        /// <summary>
+        /// Gets the Merge enumeration by value.
+        /// </summary>
+        public static Merge GetByValue(string value)
+        {
+            if (!Properties.TryGetValue(value, out var property))
+            {
+                throw new ArgumentException($"No matching Merge for value '{value}'");
+            }
+            return property;
+        }
+    }
+
+
+#pragma warning restore 1591
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
index acad588..62c19e0 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
@@ -812,6 +812,54 @@ namespace Gremlin.Net.Process.Traversal
         }
 
         /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeE step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, Edge> MergeE ()
+        {
+            return new GraphTraversal<object, Edge>().MergeE();
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeE step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, Edge> MergeE (IDictionary<object,object> m)
+        {
+            return new GraphTraversal<object, Edge>().MergeE(m);
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeE step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, Edge> MergeE (ITraversal t)
+        {
+            return new GraphTraversal<object, Edge>().MergeE(t);
+        }
+
+        /// <summary>
+        ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeV step to that traversal.
+        /// </summary>
+        public static GraphTraversal<object, Vertex> MergeV ()
+        {
+            return new GraphTraversal<object, Vertex>().MergeV();
+        }
+
+        /// <summary>
+        ///     Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public static GraphTraversal<object, Vertex> MergeV (IDictionary<object,object> m)
+        {
+            return new GraphTraversal<object, Vertex>().MergeV(m);
+        }
+
+        /// <summary>
+        ///     Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public static GraphTraversal<object, Vertex> MergeV (ITraversal t)
+        {
+            return new GraphTraversal<object, Vertex>().MergeV(t);
+        }
+
+        /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the min step to that traversal.
         /// </summary>
         public static GraphTraversal<object, E2> Min<E2>()
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs
index 3e65ad0..ee33cee 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs
@@ -55,6 +55,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary
         public static readonly DataType Cardinality = new DataType(0x16);
         public static readonly DataType Column = new DataType(0x17);
         public static readonly DataType Direction = new DataType(0x18);
+        public static readonly DataType Merge = new DataType(0x2E);
         public static readonly DataType Operator = new DataType(0x19);
         public static readonly DataType Order = new DataType(0x1A);
         public static readonly DataType Pick = new DataType(0x1B);
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs
index 87c28e6..d30b5ca 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs
@@ -60,6 +60,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary
                 {typeof(Cardinality), EnumSerializers.CardinalitySerializer},
                 {typeof(Column), EnumSerializers.ColumnSerializer},
                 {typeof(Direction), EnumSerializers.DirectionSerializer},
+                {typeof(Merge), EnumSerializers.MergeSerializer},
                 {typeof(Operator), EnumSerializers.OperatorSerializer},
                 {typeof(Order), EnumSerializers.OrderSerializer},
                 {typeof(Pick), EnumSerializers.PickSerializer},
@@ -107,6 +108,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary
                 {DataType.Cardinality, EnumSerializers.CardinalitySerializer},
                 {DataType.Column, EnumSerializers.ColumnSerializer},
                 {DataType.Direction, EnumSerializers.DirectionSerializer},
+                {DataType.Merge, EnumSerializers.MergeSerializer},
                 {DataType.Operator, EnumSerializers.OperatorSerializer},
                 {DataType.Order, EnumSerializers.OrderSerializer},
                 {DataType.Pick, EnumSerializers.PickSerializer},
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs
index d561c2a..5bc6080 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs
@@ -58,6 +58,12 @@ namespace Gremlin.Net.Structure.IO.GraphBinary.Types
             new EnumSerializer<Direction>(DataType.Direction, Direction.GetByValue);
 
         /// <summary>
+        /// A serializer for <see cref="Merge"/> values.
+        /// </summary>
+        public static readonly EnumSerializer<Merge> MergeSerializer =
+            new EnumSerializer<Merge>(DataType.Merge, Merge.GetByValue);
+
+        /// <summary>
         /// A serializer for <see cref="Operator"/> values.
         /// </summary>
         public static readonly EnumSerializer<Operator> OperatorSerializer =
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs
index 16901bd..3208966 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs
@@ -45,6 +45,7 @@ namespace Gremlin.Net.Structure.IO.GraphSON
                 {"g:Float", new FloatConverter()},
                 {"g:Double", new DoubleConverter()},
                 {"g:Direction", new DirectionDeserializer()},
+                {"g:Merge", new MergeDeserializer()},
                 {"g:UUID", new UuidDeserializer()},
                 {"g:Date", new DateDeserializer()},
                 {"g:Timestamp", new DateDeserializer()},
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/MergeDeserializer.cs
similarity index 62%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
copy to gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/MergeDeserializer.cs
index 6eb60a2..26b6869 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/MergeDeserializer.cs
@@ -1,3 +1,5 @@
+#region License
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -7,7 +9,7 @@
  * "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
+ *     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
@@ -16,16 +18,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.process.traversal.step;
-
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 
-/**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
- */
-public interface TraversalOptionParent<M, S, E> extends TraversalParent {
+#endregion
 
-    public static enum Pick {any, none}
+using System.Text.Json;
+using Gremlin.Net.Process.Traversal;
 
-    public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption);
-}
+namespace Gremlin.Net.Structure.IO.GraphSON
+{
+    internal class MergeDeserializer : IGraphSONDeserializer
+    {
+        public dynamic Objectify(JsonElement graphsonObject, GraphSONReader reader)
+        {
+            return Merge.GetByValue(graphsonObject.GetString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 03b429a..fcac51a 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -379,7 +379,14 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
 
         private static Vertex ToVertex(string name, string graphName)
         {
-            return ScenarioData.GetByGraphName(graphName).Vertices[name];
+            if (ScenarioData.GetByGraphName(graphName).Vertices.ContainsKey(name))
+            {
+                return ScenarioData.GetByGraphName(graphName).Vertices[name];
+            }
+            else
+            {
+                return new Vertex(name);
+            }
         }
 
         private static Edge ToEdge(string name, string graphName)
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
index 0209c7b..365bedb 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
@@ -87,6 +87,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                 },
                 {"g_V_properties_order", IgnoreReason.VertexPropertyNotSupportedInGherkin},
                 {"g_V_properties_order_id", IgnoreReason.VertexPropertyNotSupportedInGherkin},
+                {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+                {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+                {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+                {"g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+                {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
             };
 
         private static class Keywords
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 021f484..6a2f5ef 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -500,6 +500,32 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy(productiveKeys: new List<object> {})).V().Aggregate("a").By("foo").Cap<object>("a").Unfold<object>().Mean<object>()}}, 
                {"g_injectXnull_10_20_nullX_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,p["xx1"],p["xx2"],null).Mean<object>()}}, 
                {"g_injectXlistXnull_10_20_nullXX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Mean<object>(Scope.Local)}}, 
+               {"g_V_mergeEXlabel_self_weight_05X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) =>g.V().MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.E().HasLabel("self").Has("weight",0.5)}}, 
+               {"g_mergeEXlabel_knows_out_marko_in_vadasX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").AddV("person").Property(T.Id,101).Property("name","vadas"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko").Out("knows").Has("person","name","vadas")}}, 
+               {"g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko").OutE("knows").Has("weight",0.5).InV().Has("person","name"," [...]
+               {"g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V(), (g,p) =>g.E().HasLabel("knows").Has("weight",0.5)}}, 
+               {"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"]).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx3"]), (g,p) =>g.V(), (g,p) =>g.E().HasLabel("knows").Has("created","Y"), (g,p) =>g.E().HasLabel("knows").Has("created","N")}}, 
+               {"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"] [...]
+               {"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("created","Y"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDic [...]
+               {"g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("created","Y").AddE("knows").From("a").To("b"), (g,p) =>g.V().Has("person","name","marko").Me [...]
+               {"g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko"), (g,p) =>g.V(100).V(101).MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V(), (g,p) =>g.E()}}, 
+               {"g_injectXlabel_knows_out_marko_in_vadasX_mergeE", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").AddV("person").Property(T.Id,101).Property("name","vadas"), (g,p) =>g.Inject(p["xx1"]).MergeE(), (g,p) =>g.V().Has("person","name","marko").Out("knows").Has("person","name","vadas")}}, 
+               {"g_mergeVXlabel_person_name_stephenX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","stephen")}}, 
+               {"g_mergeVXlabel_person_name_markoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko")}}, 
+               {"g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","stephen").Has("age",19)}}, 
+               {"g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","marko").Has("age",19)}}, 
+               {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.OnCreate, (IDictionary [...]
+               {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.OnMatch, (IDictionary<object,object>) __.Select<obj [...]
+               {"g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Property("name","vadas","acl","public"), (g,p) =>g.V().Properties<object>("name").HasValue("vadas").Has("acl","public")}}, 
+               {"g_injectX0X_mergeVXlabel_person_name_stephenX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","stephen")}}, 
+               {"g_injectX0X_mergeVXlabel_person_name_markoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko")}}, 
+               {"g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","stephen").Has("age",19)}}, 
+               {"g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","marko").Has("age",19)}}, 
+               {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).Inject(0).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.On [...]
+               {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).Inject(0).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.OnMatch, (IDictionary<object,ob [...]
+               {"g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]).Property("name","vadas","acl","public"), (g,p) =>g.V().Properties<object>("name").HasValue("vadas").Has("acl","public")}}, 
+               {"g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(p["xx1"],p["xx2"]).MergeV((IDictionary<object,object>) __.Identity()), (g,p) =>g.V().Has("person","name","stephen"), (g,p) =>g.V().Has("person","name","marko"), (g,p) =>g.V()}}, 
+               {"g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(p["xx1"],p["xx2"]).MergeV(), (g,p) =>g.V().Has("person","name","stephen"), (g,p) =>g.V().Has("person","name","marko"), (g,p) =>g.V()}}, 
                {"g_V_age_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Min<object>()}}, 
                {"g_V_foo_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Min<object>()}}, 
                {"g_V_name_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Min<object>()}}, 
@@ -807,7 +833,6 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_outXfollowedByX_group_byXsongTypeX_byXbothE_group_byXlabelX_byXweight_sumXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("followedBy").Group<object,object>().By("songType").By(__.BothE().Group<object,object>().By(T.Label).By(__.Values<object>("weight").Sum<object>()))}}, 
                {"g_V_groupXmX_byXnameX_byXinXknowsX_nameX_capXmX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group("m").By("name").By(__.In("knows").Values<object>("name")).Cap<object>("m")}}, 
                {"g_V_group_byXlabelX_byXbothE_groupXaX_byXlabelX_byXweight_sumX_weight_sumX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By(T.Label).By(__.BothE().Group("a").By(T.Label).By(__.Values<object>("weight").Sum<object>()).Values<object>("weight").Sum<object>())}}, 
-               {"g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithSideEffect("a",p["xx1"]).V().Group("a").By("name").By(__.OutE().Label().Fold()).Cap<object>("a").Unfold<object>().Group<object,object>().By(Column.Keys).By(__.Select<object>(Column.Values).Order(Scope.Local).By(Order.Asc))}}, 
                {"g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").As("p").Out("created").Group<object,object>().By("name").By(__.Select<object>("p").Values<object>("age").Sum<object>())}}, 
                {"g_V_hasLabelXpersonX_asXpX_outXcreatedX_groupXaX_byXnameX_byXselectXpX_valuesXageX_sumX_capXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").As("p").Out("created").Group("a").By("name").By(__.Select<object>("p").Values<object>("age").Sum<object>()).Cap<object>("a")}}, 
                {"g_V_group_byXlabelX_byXlabel_countX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By(__.Label()).By(__.Label().Count())}}, 
@@ -830,7 +855,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person","name","marko").Both("knows").GroupCount<object>().By(__.Values<object>("name").Fold())}}, 
                {"g_VX1X_out_injectXv2X_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Inject((Vertex) p["v2"]).Values<object>("name")}}, 
                {"g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Values<object>("name").Inject("daniel").As("a").Map<object>((IFunction) p["l1"]).Path()}}, 
-               {"g_VX1X_injectXg_VX4XX_out_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Inject((Vertex) p["v4"]).Out().Values<object>("name")}}, 
+               {"g_VX1X_injectXg_VX4XX_out_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Inject((Vertex) p["v2"]).Out().Values<object>("name")}}, 
                {"g_injectXnull_1_3_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,1,3,null)}}, 
                {"g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(10,20,null,20,10,10).GroupCount("x").Dedup().As("y").Project<object>("a","b").By().By(__.Select<object>("x").Select<object>(__.Select<object>("y")))}}, 
                {"g_injectXname_marko_age_nullX_selectXname_ageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Select<object>("name","age")}}, 
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
index 2fa881b..8f1bbac 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
@@ -70,6 +70,13 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
         /// <summary>
         /// Need a Gherkin parser for VertexProperty results: https://issues.apache.org/jira/browse/TINKERPOP-2686
         /// </summary>
-        VertexPropertyNotSupportedInGherkin
+        VertexPropertyNotSupportedInGherkin,
+
+       /// <summary>
+       /// VarAsBindingASTTransformation isn't capable of properly casting arguments so the DotNetTranslator can't
+       /// produce the right Gremlin. These tests could be static translated but there are a lot of them so it would
+       /// be better to solve this more directly.
+       /// </summary>
+       MergeVEWithTraversalNotSupportedInTranslation
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs
index 7978005..eecb385 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs
@@ -535,6 +535,21 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary
             
             Assert.Equal(expected, actual);
         }
+
+        [Fact]
+        public async Task TestMerge()
+        {
+            var expected = Merge.OnCreate;
+            var writer = CreateGraphBinaryWriter();
+            var reader = CreateGraphBinaryReader();
+            var serializationStream = new MemoryStream();
+
+            await writer.WriteAsync(expected, serializationStream);
+            serializationStream.Position = 0;
+            var actual = await reader.ReadAsync(serializationStream);
+
+            Assert.Equal(expected, actual);
+        }
         
         [Fact]
         public async Task TestOperator()
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
index 5bc232b..2c33a1e 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
@@ -312,6 +312,18 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
         
             Assert.Equal(Direction.Out, deserializedValue);
         }
+
+        [Theory, MemberData(nameof(Versions))]
+        public void ShouldDeserializeMerge(int version)
+        {
+            const string serializedValue = "{\"@type\":\"g:Merge\",\"@value\":\"onMatch\"}";
+            var reader = CreateStandardGraphSONReader(version);
+
+            var jsonElement = JsonSerializer.Deserialize<JsonElement>(serializedValue);
+            var deserializedValue = reader.ToObject(jsonElement);
+
+            Assert.Equal(Merge.OnMatch, deserializedValue);
+        }
         
         [Fact]
         public void ShouldDeserializePathFromGraphSON2()
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
index ce593f7..9247831 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
@@ -258,7 +258,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
         }
 
         [Theory, MemberData(nameof(Versions))]
-        public void ShouldSerializeEnum(int version)
+        public void ShouldSerializeDirection(int version)
         {
             var writer = CreateGraphSONWriter(version);
 
@@ -269,6 +269,17 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
         }
 
         [Theory, MemberData(nameof(Versions))]
+        public void ShouldSerializeMerge(int version)
+        {
+            var writer = CreateGraphSONWriter(version);
+
+            var serializedEnum = writer.WriteObject(Merge.OnMatch);
+
+            var expectedGraphSON = "{\"@type\":\"g:Merge\",\"@value\":\"onMatch\"}";
+            Assert.Equal(expectedGraphSON, serializedEnum);
+        }
+
+        [Theory, MemberData(nameof(Versions))]
         public void ShouldSerializeList(int version)
         {
             var writer = CreateGraphSONWriter(version);
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java
index dc10baf..aeebf35 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.driver.message;
 
+import org.apache.tinkerpop.gremlin.driver.Tokens;
 import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
 
 import java.util.HashMap;
@@ -34,7 +35,7 @@ public final class RequestMessage {
     /**
      * An "invalid" message.  Used internally only.
      */
-    public static final RequestMessage INVALID = new RequestMessage("invalid");
+    public static final RequestMessage INVALID = new RequestMessage(Tokens.OPS_INVALID);
 
     private final UUID requestId;
     private final String op;
diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java
index 3fd54e2..1800331 100644
--- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java
+++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java
@@ -27,13 +27,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
@@ -221,7 +221,7 @@ public class GraphBinaryReaderWriterRoundTripTest {
                 new Object[] {"Operator", Operator.sum, null},
                 new Object[] {"Operator", Operator.div, null},
                 new Object[] {"Order", Order.desc, null},
-                new Object[] {"Pick", TraversalOptionParent.Pick.any, null},
+                new Object[] {"Pick", Pick.any, null},
                 new Object[] {"Pop", Pop.mixed, null},
                 new Object[] {"Scope", Scope.global, null},
                 new Object[] {"T", T.label, null},
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
index 1da973d..ea3c65a 100644
--- a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
@@ -20,6 +20,7 @@
 package org.apache.tinkerpop.gremlin.groovy.jsr223.ast
 
 import org.apache.tinkerpop.gremlin.process.traversal.Order
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
 import org.apache.tinkerpop.gremlin.process.traversal.Translator
@@ -115,7 +116,11 @@ class VarAsBindingASTTransformation implements ASTTransformation {
                                 case GraphTraversal.Symbols.by:
                                     if (i == 1) bindingValue = new PropertyExpression(new ClassExpression(new ClassNode(Order)), "desc")
                                     break
-                                case GraphTraversal.Symbols.fail:
+                                case GraphTraversal.Symbols.mergeE:
+                                case GraphTraversal.Symbols.mergeV:
+                                    bindingValue = new MethodCallExpression(new ClassExpression(new ClassNode(Collections)), "emptyMap", new TupleExpression())
+                                    break
+                                case GraphTraversal.Symbols.option:
                                     if (i == 1) bindingValue = new MethodCallExpression(new ClassExpression(new ClassNode(Collections)), "emptyMap", new TupleExpression())
                                     break
                             }
diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
index 3baf20e..66b2dbd 100644
--- a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
+++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
@@ -484,6 +484,8 @@ public class GremlinGroovyScriptEngineTest {
         final Traversal.Admin<Vertex, Vertex> t = (Traversal.Admin<Vertex, Vertex>)
                 engine.eval("g.V(v1Id).has(\"person\",\"age\",29).has('person','active',x).in(\"knows\")." +
                         System.lineSeparator() +
+                        "mergeE(m1).mergeV(m2).option(Merge.onCreate,m3)." +
+                        System.lineSeparator() +
                         "choose(__.out().count()).option(two, __.values(\"name\")).option(three, __.values(\"age\"))." +
                         System.lineSeparator() +
                         "filter(outE().count().is(y))."  +
@@ -498,15 +500,18 @@ public class GremlinGroovyScriptEngineTest {
         final String gremlinAsPython = translator.translate(bytecode).getScript();
 
         final Map<String,Object> bytecodeBindings = bytecode.getBindings();
-        assertEquals(7, bytecodeBindings.size());
+        assertEquals(10, bytecodeBindings.size());
         assertThat(bytecodeBindings.containsKey("x"), is(true));
         assertThat(bytecodeBindings.containsKey("y"), is(true));
         assertThat(bytecodeBindings.containsKey("v1Id"), is(true));
         assertThat(bytecodeBindings.containsKey("l"), is(true));
         assertThat(bytecodeBindings.containsKey("o"), is(true));
+        assertThat(bytecodeBindings.containsKey("m1"), is(true));
+        assertThat(bytecodeBindings.containsKey("m2"), is(true));
+        assertThat(bytecodeBindings.containsKey("m3"), is(true));
         assertThat(bytecodeBindings.containsKey("two"), is(true));
         assertThat(bytecodeBindings.containsKey("three"), is(true));
 
-        assertEquals("g.V(v1Id).has('person','age',29).has('person','active',x).in_('knows').choose(__.out().count()).option(two,__.name).option(three,__.age).filter_(__.outE().count().is_(y)).map(l).order().by('name',o)", gremlinAsPython);
+        assertEquals("g.V(v1Id).has('person','age',29).has('person','active',x).in_('knows').mergeE(m1).mergeV(m2).option(Merge.onCreate,m3).choose(__.out().count()).option(two,__.name).option(three,__.age).filter_(__.outE().count().is_(y)).map(l).order().by('name',o)", gremlinAsPython);
     }
 }
\ No newline at end of file
diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy
index 013f840..1b52ea4 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -31,6 +31,16 @@ import java.nio.file.Paths
 
 import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
 
+// getting an exception like:
+// > InvocationTargetException: javax.script.ScriptException: groovy.lang.MissingMethodException: No signature of
+// > method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.mergeE() is applicable for
+// > argument types: (String) values: [4ffdea36-4a0e-4681-acba-e76875d1b25b]
+// usually means bindings are not being extracted properly by VarAsBindingASTTransformation which typically happens
+// when a step is taking an argument that cannot properly resolve to the type required by the step itself. there are
+// special cases in that VarAsBindingASTTransformation class which might need to be adjusted. Editing the
+// GremlinGroovyScriptEngineTest#shouldProduceBindingsForVars() with the failing step and argument can typically make
+// this issue relatively easy to debug and enforce.
+
 // file is overwritten on each generation
 radishGremlinFile = new File("${projectBaseDir}/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js")
 
@@ -84,6 +94,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
                     '    IN: traversalModule.direction.in,\n' +
                     '    OUT: traversalModule.direction.out\n' +
                     '};\n' +
+                    'const Merge = traversalModule.merge;\n' +
                     'const P = traversalModule.P;\n' +
                     'const Pick = traversalModule.pick\n' +
                     'const Pop = traversalModule.pop\n' +
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 6c49ec5..ae3d7f3 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
@@ -212,6 +212,16 @@ class GraphTraversalSource {
     const b = new Bytecode(this.bytecode).addStep('addE', args);
     return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
   }
+
+  /**
+   * mergeV GraphTraversalSource step method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  mergeE(...args) {
+    const b = new Bytecode(this.bytecode).addStep('mergeE', args);
+    return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
+  }
   
   /**
    * addV GraphTraversalSource step method.
@@ -222,6 +232,16 @@ class GraphTraversalSource {
     const b = new Bytecode(this.bytecode).addStep('addV', args);
     return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
   }
+
+  /**
+   * mergeV GraphTraversalSource step method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  mergeV(...args) {
+    const b = new Bytecode(this.bytecode).addStep('mergeV', args);
+    return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
+  }
   
   /**
    * inject GraphTraversalSource step method.
@@ -810,6 +830,26 @@ class GraphTraversal extends Traversal {
     this.bytecode.addStep('mean', args);
     return this;
   }
+
+  /**
+   * Graph traversal mergeE method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  mergeE(...args) {
+    this.bytecode.addStep("mergeE", args);
+    return this;
+  }
+
+  /**
+   * Graph traversal mergeV method.
+   * @param {...Object} args
+   * @returns {GraphTraversal}
+   */
+  mergeV(...args) {
+    this.bytecode.addStep("mergeV", args);
+    return this;
+  }
   
   /**
    * Graph traversal min method.
@@ -1365,6 +1405,8 @@ const statics = {
   math: (...args) => callOnEmptyTraversal('math', args),
   max: (...args) => callOnEmptyTraversal('max', args),
   mean: (...args) => callOnEmptyTraversal('mean', args),
+  mergeE: (...args) => callOnEmptyTraversal("mergeE", args),
+  mergeV: (...args) => callOnEmptyTraversal("mergeV", args),
   min: (...args) => callOnEmptyTraversal('min', args),
   not: (...args) => callOnEmptyTraversal('not', args),
   optional: (...args) => callOnEmptyTraversal('optional', args),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
index f72c3f9..bbffeea 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
@@ -496,6 +496,7 @@ module.exports = {
   direction: toEnum('Direction', 'BOTH IN OUT'),
   graphSONVersion: toEnum('GraphSONVersion', 'V1_0 V2_0 V3_0'),
   gryoVersion: toEnum('GryoVersion', 'V1_0 V3_0'),
+  merge: toEnum('Merge', 'onCreate onMatch'),
   operator: toEnum('Operator', 'addAll and assign div max min minus mult or sum sumLong'),
   order: toEnum('Order', 'asc desc shuffle'),
   pick: toEnum('Pick', 'any none'),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index d500feb..ba23650 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -273,7 +273,13 @@ function toNumeric(stringValue) {
 }
 
 function toVertex(name) {
-  return this.getData().vertices.get(name);
+  // some vertices are cached, like those from toy graphs but some are just references. if they are
+  // not cached then they are meant to be references.
+  const vertices = this.getData().vertices;
+  if (vertices.has(name))
+    return this.getData().vertices.get(name);
+  else
+    return new graphModule.Vertex(name, "vertex")
 }
 
 function toVertexId(name) {
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 0a452a6..2d6db86 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
@@ -36,6 +36,7 @@ const Direction = {
     IN: traversalModule.direction.in,
     OUT: traversalModule.direction.out
 };
+const Merge = traversalModule.merge;
 const P = traversalModule.P;
 const Pick = traversalModule.pick
 const Pop = traversalModule.pop
@@ -488,6 +489,32 @@ const gremlins = {
     g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean: [function({g}) { return g.withStrategies(new ProductiveByStrategy({productiveKeys:[]})).V().aggregate("a").by("foo").cap("a").unfold().mean() }], 
     g_injectXnull_10_20_nullX_mean: [function({g, xx1, xx2}) { return g.inject(null,xx1,xx2,null).mean() }], 
     g_injectXlistXnull_10_20_nullXX_meanXlocalX: [function({g, xx1}) { return g.inject(xx1).mean(Scope.local) }], 
+    g_V_mergeEXlabel_self_weight_05X: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29) }, function({g, xx1}) { return g.V().mergeE(xx1) }, function({g, xx1}) { return g.E().hasLabel("self").has("weight",0.5) }], 
+    g_mergeEXlabel_knows_out_marko_in_vadasX: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko").addV("person").property(T.id,101).property("name","vadas") }, function({g, xx1}) { return g.mergeE(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko").out("knows").has("person","name","vadas") }], 
+    g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b") }, function({g, xx1}) { return g.mergeE(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko").outE("knows").has("weight",0.5).inV().has("person","name","vadas") }, function({g, xx1}) { return g.V().has("person","na [...]
+    g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X: [function({g, xx1}) { return g.mergeE(xx1) }, function({g, xx1}) { return g.V() }, function({g, xx1}) { return g.E().hasLabel("knows").has("weight",0.5) }], 
+    g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX: [function({g, xx1, xx3, xx2}) { return g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3) }, function({g, xx1, xx3, xx2}) { return g.V() }, function({g, xx1, xx3, xx2}) { return g.E().hasLabel("knows").has("created","Y") }, function({g, xx1, xx3, xx2}) { return g.E().hasLabel("knows").has("created","N") }], 
+    g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists: [function({g, xx1, xx3, xx2}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b") }, function({g, xx1, xx3, xx2}) { return g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3) }, function({g, xx1, xx3, xx2}) { return g.V() }, function({g, xx1, xx [...]
+    g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: [function({g, xx1, xx3, xx2}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b").property("created","Y") }, function({g, xx1, xx3, xx2}) { return g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3) }, function({g, xx1, xx3, xx2}) { retu [...]
+    g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: [function({g, xx1, xx3, xx2}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b").property("created","Y").addE("knows").from_("a").to("b") }, function({g, xx1, xx3, xx2}) { return g.V().has("person","name","marko").mergeE(xx1).option(Merge.onCre [...]
+    g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko") }, function({g, xx1}) { return g.V(100).V(101).mergeE(xx1) }, function({g, xx1}) { return g.V() }, function({g, xx1}) { return g.E() }], 
+    g_injectXlabel_knows_out_marko_in_vadasX_mergeE: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko").addV("person").property(T.id,101).property("name","vadas") }, function({g, xx1}) { return g.inject(xx1).mergeE() }, function({g, xx1}) { return g.V().has("person","name","marko").out("knows").has("person","name","vadas") }], 
+    g_mergeVXlabel_person_name_stephenX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","stephen") }], 
+    g_mergeVXlabel_person_name_markoX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko") }], 
+    g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onCreate,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen").has("age",19) }], 
+    g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onMatch,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }], 
+    g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).mergeV(__.select("c")).option(Merge.onCreate,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen").has("age",19) }], 
+    g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).mergeV(__.select("c")).option(Merge.onMatch,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }], 
+    g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.mergeV(xx1).property("name","vadas","acl","public") }, function({g, xx1}) { return g.V().properties("name").hasValue("vadas").has("acl","public") }], 
+    g_injectX0X_mergeVXlabel_person_name_stephenX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.inject(0).mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","stephen") }], 
+    g_injectX0X_mergeVXlabel_person_name_markoX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.inject(0).mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko") }], 
+    g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(0).mergeV(xx1).option(Merge.onCreate,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen").has("age",19) }], 
+    g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(0).mergeV(xx1).option(Merge.onMatch,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }], 
+    g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).inject(0).mergeV(__.select("c")).option(Merge.onCreate,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","s [...]
+    g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).inject(0).mergeV(__.select("c")).option(Merge.onMatch,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }], 
+    g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.inject(0).mergeV(xx1).property("name","vadas","acl","public") }, function({g, xx1}) { return g.V().properties("name").hasValue("vadas").has("acl","public") }], 
+    g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(xx1,xx2).mergeV(__.identity()) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen") }, function({g, xx1, xx2}) { return g.V().has("person","name","marko") }, function({g, xx1, xx2}) { return g.V() }], 
+    g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(xx1,xx2).mergeV() }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen") }, function({g, xx1, xx2}) { return g.V().has("person","name","marko") }, function({g, xx1, xx2}) { return g.V() }], 
     g_V_age_min: [function({g}) { return g.V().values("age").min() }], 
     g_V_foo_min: [function({g}) { return g.V().values("foo").min() }], 
     g_V_name_min: [function({g}) { return g.V().values("name").min() }], 
@@ -795,7 +822,6 @@ const gremlins = {
     g_V_outXfollowedByX_group_byXsongTypeX_byXbothE_group_byXlabelX_byXweight_sumXX: [function({g}) { return g.V().out("followedBy").group().by("songType").by(__.bothE().group().by(T.label).by(__.values("weight").sum())) }], 
     g_V_groupXmX_byXnameX_byXinXknowsX_nameX_capXmX: [function({g}) { return g.V().group("m").by("name").by(__.in_("knows").values("name")).cap("m") }], 
     g_V_group_byXlabelX_byXbothE_groupXaX_byXlabelX_byXweight_sumX_weight_sumX: [function({g}) { return g.V().group().by(T.label).by(__.bothE().group("a").by(T.label).by(__.values("weight").sum()).values("weight").sum()) }], 
-    g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX: [function({g, xx1}) { return g.withSideEffect("a",xx1).V().group("a").by("name").by(__.outE().label().fold()).cap("a").unfold().group().by(Column.keys).by(__.select(Column.values).order(Scope.local).by(Order.asc)) }], 
     g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX: [function({g}) { return g.V().hasLabel("person").as("p").out("created").group().by("name").by(__.select("p").values("age").sum()) }], 
     g_V_hasLabelXpersonX_asXpX_outXcreatedX_groupXaX_byXnameX_byXselectXpX_valuesXageX_sumX_capXaX: [function({g}) { return g.V().hasLabel("person").as("p").out("created").group("a").by("name").by(__.select("p").values("age").sum()).cap("a") }], 
     g_V_group_byXlabelX_byXlabel_countX: [function({g}) { return g.V().group().by(__.label()).by(__.label().count()) }], 
@@ -818,7 +844,7 @@ const gremlins = {
     g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX: [function({g}) { return g.V().has("person","name","marko").both("knows").groupCount().by(__.values("name").fold()) }], 
     g_VX1X_out_injectXv2X_name: [function({g, vid1, v2}) { return g.V(vid1).out().inject(v2).values("name") }], 
     g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path: [function({g, l1, vid1}) { return g.V(vid1).out().values("name").inject("daniel").as("a").map(l1).path() }], 
-    g_VX1X_injectXg_VX4XX_out_name: [function({g, vid1, v4}) { return g.V(vid1).inject(v4).out().values("name") }], 
+    g_VX1X_injectXg_VX4XX_out_name: [function({g, vid1, v2}) { return g.V(vid1).inject(v2).out().values("name") }], 
     g_injectXnull_1_3_nullX: [function({g}) { return g.inject(null,1,3,null) }], 
     g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX: [function({g}) { return g.inject(10,20,null,20,10,10).groupCount("x").dedup().as("y").project("a","b").by().by(__.select("x").select(__.select("y"))) }], 
     g_injectXname_marko_age_nullX_selectXname_ageX: [function({g, xx1}) { return g.inject(xx1).select("name","age") }], 
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index 78d8928..7095e7b 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -97,6 +97,8 @@ traversalSourceSpawnMethod
 	| traversalSourceSpawnMethod_addV
 	| traversalSourceSpawnMethod_E
 	| traversalSourceSpawnMethod_V
+	| traversalSourceSpawnMethod_mergeE
+	| traversalSourceSpawnMethod_mergeV
 	| traversalSourceSpawnMethod_inject
     | traversalSourceSpawnMethod_io
     ;
@@ -128,6 +130,16 @@ traversalSourceSpawnMethod_io
     : 'io' LPAREN stringLiteral RPAREN
     ;
 
+traversalSourceSpawnMethod_mergeV
+    : 'mergeV' LPAREN genericLiteralMap RPAREN #traversalSourceSpawnMethod_mergeV_Map
+    | 'mergeV' LPAREN nestedTraversal RPAREN #traversalSourceSpawnMethod_mergeV_Traversal
+    ;
+
+traversalSourceSpawnMethod_mergeE
+    : 'mergeE' LPAREN genericLiteralMap RPAREN #traversalSourceSpawnMethod_mergeE_Map
+    | 'mergeE' LPAREN nestedTraversal RPAREN #traversalSourceSpawnMethod_mergeE_Traversal
+    ;
+
 chainedTraversal
     : traversalMethod
     | chainedTraversal DOT traversalMethod
@@ -157,6 +169,8 @@ traversalMethod
 	: traversalMethod_V
 	| traversalMethod_addE
 	| traversalMethod_addV
+	| traversalMethod_mergeE
+	| traversalMethod_mergeV
 	| traversalMethod_aggregate
 	| traversalMethod_and
 	| traversalMethod_as
@@ -272,6 +286,19 @@ traversalMethod_addV
 	| 'addV' LPAREN nestedTraversal RPAREN #traversalMethod_addV_Traversal
 	;
 
+traversalMethod_mergeV
+    : 'mergeV' LPAREN RPAREN #traversalMethod_mergeV_empty
+    | 'mergeV' LPAREN genericLiteralMap RPAREN #traversalMethod_mergeV_Map
+    | 'mergeV' LPAREN nestedTraversal RPAREN #traversalMethod_mergeV_Traversal
+    ;
+
+traversalMethod_mergeE
+    : 'mergeE' LPAREN RPAREN #traversalMethod_mergeE_empty
+    | 'mergeE' LPAREN genericLiteralMap RPAREN #traversalMethod_mergeE_Map
+    | 'mergeE' LPAREN nestedTraversal RPAREN #traversalMethod_mergeE_Traversal
+    ;
+
+
 traversalMethod_aggregate
 	: 'aggregate' LPAREN traversalScope COMMA stringLiteral RPAREN #traversalMethod_aggregate_Scope_String
 	| 'aggregate' LPAREN stringLiteral RPAREN #traversalMethod_aggregate_String
@@ -531,6 +558,8 @@ traversalMethod_not
 
 traversalMethod_option
 	: 'option' LPAREN traversalPredicate COMMA nestedTraversal RPAREN #traversalMethod_option_Predicate_Traversal
+	| 'option' LPAREN traversalMerge COMMA genericLiteralMap RPAREN #traversalMethod_option_Merge_Map
+	| 'option' LPAREN traversalMerge COMMA nestedTraversal RPAREN #traversalMethod_option_Merge_Traversal
 	| 'option' LPAREN genericLiteral COMMA nestedTraversal RPAREN #traversalMethod_option_Object_Traversal
 	| 'option' LPAREN nestedTraversal RPAREN #traversalMethod_option_Traversal
 	;
@@ -837,6 +866,11 @@ traversalToken
     | 'value' | 'T.value'
     ;
 
+traversalMerge
+    : 'onCreate' | 'Merge.onCreate'
+    | 'onMatch' | 'Merge.onMatch'
+    ;
+
 traversalOrder
     : 'incr' | 'Order.incr'
     | 'decr' | 'Order.decr'
@@ -1310,6 +1344,7 @@ genericLiteral
 	| traversalCardinality
 	| traversalDirection
 	| traversalOptionParent
+	| structureVertex
 	| genericLiteralCollection
 	| genericLiteralRange
 	| nestedTraversal
diff --git a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
index ba7c749..6c091e5 100644
--- a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
+++ b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
@@ -105,6 +105,8 @@ public class DocumentationReader {
                 replace("vMarko", "\"marko\"").
                 replace("vPeter", "\"peter\"").
                 replace("vStephen", "\"stephen\"").
+                replace("v1", "new Vertex(1,\"vertex\")").
+                replace("v2", "new Vertex(2,\"vertex\")").
                 replace("input.head()", "\"head\"").
                 replace("input.tail().size()", "6").
                 replace("input.tail()", "\"tail\"").
diff --git a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
index b3d3f29..b241c59 100644
--- a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
+++ b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
@@ -49,10 +49,20 @@ public class ReferenceGrammarTest extends AbstractGrammarTest {
     private static final String featureDir = Paths.get("..", "gremlin-test", "features").toString();
     private static final String docsDir = Paths.get("..", "docs", "src").toString();
 
-    private static final Pattern vertexPattern = Pattern.compile(".*v\\d.*");
     private static final Pattern edgePattern = Pattern.compile(".*e\\d.*");
 
     private static final List<Pair<Pattern, BiFunction<String,String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, BiFunction<String,String,String>>>() {{
+        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> {
+            // can't handled embedded maps because of the string replace below on the curly braces
+            final String[] items = v.replace("{", "").replace("}", "").split(",");
+            final String listItems = Stream.of(items).map(String::trim).map(x -> {
+                final String[] pair = x.split(":");
+                final String convertedKey = String.format("%s", pair[0]);
+                final String convertedValue = String.format("%s", pair[1]);
+                return String.format("%s:%s", convertedKey, convertedValue);
+            }).collect(Collectors.joining(","));
+            return String.format("[%s]", listItems);
+        }));
         add(Pair.with(Pattern.compile("l\\[\\]"), (k,v) -> "[]"));
         add(Pair.with(Pattern.compile("l\\[(.*)\\]"), (k,v) -> {
             final String[] items = v.split(",");
@@ -60,6 +70,7 @@ public class ReferenceGrammarTest extends AbstractGrammarTest {
             return String.format("[%s]", listItems);
         }));
         add(Pair.with(Pattern.compile("v\\[(.+)\\]"), (k,v) -> "\"1\""));
+        add(Pair.with(Pattern.compile("v(\\d)"), (k,v) -> String.format("new Vertex(%s, \"vertex\")", v)));
         add(Pair.with(Pattern.compile("e\\[(.+)\\]"), (k,v) -> "\"1\""));
         add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.?.*"), (k,v) -> v));
         add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> v.replace('{','[').replace('}', ']')));
@@ -89,11 +100,12 @@ public class ReferenceGrammarTest extends AbstractGrammarTest {
 
     @Test
     public void test_parse() {
+        // can't handle maps with complex embedded types at the moment - basically one Gremlin statement at this point
+        assumeThat("Complex embedded types are not supported", query.contains("l[\"666\"]"), is(false));
         assumeThat("Lambdas are not supported", query.contains("Lambda.function("), is(false));
         // start of a closure
         assumeThat("Lambdas are not supported", query.contains("{"), is(false));
         assumeThat("withComputer() step is not supported", query.startsWith("g.withComputer("), is(false));
-        assumeThat("Vertex instances are not supported", vertexPattern.matcher(query).matches(), is(false));
         assumeThat("Edge instances are not supported", edgePattern.matcher(query).matches(), is(false));
         assumeThat("fill() terminator is not supported", query.contains("fill("), is(false));
         assumeThat("withoutStrategies() is not supported", query.contains("withoutStrategies("), is(false));
diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy
index f0a3d45..0c6dd42 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -31,6 +31,16 @@ import java.nio.file.Paths
 
 import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
 
+// getting an exception like:
+// > InvocationTargetException: javax.script.ScriptException: groovy.lang.MissingMethodException: No signature of
+// > method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.mergeE() is applicable for
+// > argument types: (String) values: [4ffdea36-4a0e-4681-acba-e76875d1b25b]
+// usually means bindings are not being extracted properly by VarAsBindingASTTransformation which typically happens
+// when a step is taking an argument that cannot properly resolve to the type required by the step itself. there are
+// special cases in that VarAsBindingASTTransformation class which might need to be adjusted. Editing the
+// GremlinGroovyScriptEngineTest#shouldProduceBindingsForVars() with the failing step and argument can typically make
+// this issue relatively easy to debug and enforce.
+
 // file is overwritten on each generation
 radishGremlinFile = new File("${projectBaseDir}/gremlin-python/src/main/python/radish/gremlin.py")
 
@@ -77,7 +87,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
                     'from gremlin_python.process.traversal import TraversalStrategy\n' +
                     'from gremlin_python.process.graph_traversal import __\n' +
                     'from gremlin_python.structure.graph import Graph\n' +
-                    'from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions\n')
+                    'from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions\n')
 
     // Groovy can't process certain null oriented calls because it gets confused with the right overload to call
     // at runtime. using this approach for now as these are the only such situations encountered so far. a better
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 525bc9d..c8e8ae4 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
@@ -144,6 +144,16 @@ class GraphTraversalSource(object):
         traversal.bytecode.add_step("addV", *args)
         return traversal
 
+    def mergeV(self, *args):
+        traversal = self.get_graph_traversal()
+        traversal.bytecode.add_step("mergeV", *args)
+        return traversal
+
+    def mergeE(self, *args):
+        traversal = self.get_graph_traversal()
+        traversal.bytecode.add_step("mergeE", *args)
+        return traversal
+
     def inject(self, *args):
         traversal = self.get_graph_traversal()
         traversal.bytecode.add_step("inject", *args)
@@ -401,6 +411,14 @@ class GraphTraversal(Traversal):
         self.bytecode.add_step("mean", *args)
         return self
 
+    def mergeE(self, *args):
+        self.bytecode.add_step("mergeE", *args)
+        return self
+
+    def mergeV(self, *args):
+        self.bytecode.add_step("mergeV", *args)
+        return self
+
     def min_(self, *args):
         self.bytecode.add_step("min", *args)
         return self
@@ -827,6 +845,14 @@ class __(object, metaclass=MagicType):
         return cls.graph_traversal(None, None, Bytecode()).mean(*args)
 
     @classmethod
+    def mergeE(cls, *args):
+        return cls.graph_traversal(None, None, Bytecode()).mergeE(*args)
+
+    @classmethod
+    def mergeV(cls, *args):
+        return cls.graph_traversal(None, None, Bytecode()).mergeV(*args)
+
+    @classmethod
     def min_(cls, *args):
         return cls.graph_traversal(None, None, Bytecode()).min_(*args)
 
@@ -1254,6 +1280,14 @@ def mean(*args):
     return __.mean(*args)
 
 
+def mergeE(*args):
+    return __.mergeE(*args)
+
+
+def mergeV(*args):
+    return __.mergeV(*args)
+
+
 def min_(*args):
     return __.min_(*args)
 
@@ -1514,6 +1548,10 @@ statics.add_static('max_', max_)
 
 statics.add_static('mean', mean)
 
+statics.add_static('mergeE', mergeE)
+
+statics.add_static('mergeV', mergeV)
+
 statics.add_static('min_', min_)
 
 statics.add_static('not_', not_)
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index f7bd31d..e24948c 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -152,6 +152,11 @@ GryoVersion = Enum('GryoVersion', ' V1_0 V3_0')
 statics.add_static('V1_0', GryoVersion.V1_0)
 statics.add_static('V3_0', GryoVersion.V3_0)
 
+Merge = Enum('Merge', ' onCreate onMatch')
+
+statics.add_static('onCreate', Merge.onCreate)
+statics.add_static('onMatch', Merge.onMatch)
+
 Order = Enum('Order', ' asc desc shuffle')
 
 statics.add_static('shuffle', Order.shuffle)
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
index a5a258a..f39197a 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
@@ -33,8 +33,8 @@ from datetime import timedelta
 from gremlin_python import statics
 from gremlin_python.statics import FloatType, FunctionType, IntType, LongType, TypeType, DictType, ListType, SetType, \
                                    SingleByte, ByteBufferType, GremlinType, SingleChar
-from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardinality, Column, Direction, Operator, \
-                                             Order, Pick, Pop, P, Scope, TextP, Traversal, Traverser, \
+from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardinality, Column, Direction, Merge, \
+                                             Operator, Order, Pick, Pop, P, Scope, TextP, Traversal, Traverser, \
                                              TraversalStrategy, T
 from gremlin_python.process.graph_traversal import GraphTraversal
 from gremlin_python.structure.graph import Graph, Edge, Property, Vertex, VertexProperty, Path
@@ -96,6 +96,7 @@ class DataType(Enum):
     tree = 0x2b                   # not supported - no tree object in Python yet
     metrics = 0x2c
     traversalmetrics = 0x2d
+    merge = 0x2e
     char = 0x80
     duration = 0x81
     inetaddress = 0x82            # todo
@@ -850,6 +851,11 @@ class PSerializer(_GraphBinaryTypeIO):
         return to_extend
 
 
+class MergeIO(_EnumIO):
+    graphbinary_type = DataType.merge
+    python_type = Merge
+
+
 class ScopeIO(_EnumIO):
     graphbinary_type = DataType.scope
     python_type = Scope
diff --git a/gremlin-python/src/main/python/radish/feature_steps.py b/gremlin-python/src/main/python/radish/feature_steps.py
index 66bfc45..6d0a72d 100644
--- a/gremlin-python/src/main/python/radish/feature_steps.py
+++ b/gremlin-python/src/main/python/radish/feature_steps.py
@@ -20,7 +20,7 @@
 import json
 import re
 from gremlin_python.statics import long
-from gremlin_python.structure.graph import Path
+from gremlin_python.structure.graph import Path, Vertex
 from gremlin_python.process.anonymous_traversal import traversal
 from gremlin_python.process.graph_traversal import __
 from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions
@@ -221,7 +221,11 @@ def __find_cached_element(ctx, graph_name, identifier, element_type):
     else:
         cache = ctx.lookup_v[graph_name] if element_type == "v" else ctx.lookup_e[graph_name]
 
-    return cache[identifier]
+    # try to lookup the element - if it can't be found then it must be a reference Vertex
+    if identifier in cache:
+        return cache[identifier]
+    else:
+        return Vertex(identifier)
 
 
 def _convert_results(val):
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 5ec3509..16ee2a2 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -29,7 +29,7 @@ from gremlin_python.process.anonymous_traversal import traversal
 from gremlin_python.process.traversal import TraversalStrategy
 from gremlin_python.process.graph_traversal import __
 from gremlin_python.structure.graph import Graph
-from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions
+from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions
 
 world.gremlins = {
     'g_V_branchXlabel_eq_person__a_bX_optionXa__ageX_optionXb__langX_optionXb__nameX': [(lambda g, l1=None:g.V().branch(l1).option('a',__.age).option('b',__.lang).option('b',__.name))], 
@@ -473,6 +473,32 @@ world.gremlins = {
     'g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean': [(lambda g:g.withStrategies(*[TraversalStrategy('ProductiveByStrategy',{'productiveKeys':[],'strategy':'org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy'}, 'org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy')]).V().aggregate('a').by('foo').cap('a').unfold().mean())], 
     'g_injectXnull_10_20_nullX_mean': [(lambda g, xx1=None,xx2=None:g.inject(None,xx1,xx2,None).mean())], 
     'g_injectXlistXnull_10_20_nullXX_meanXlocalX': [(lambda g, xx1=None:g.inject(xx1).mean(Scope.local))], 
+    'g_V_mergeEXlabel_self_weight_05X': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29)), (lambda g, xx1=None:g.V().mergeE(xx1)), (lambda g, xx1=None:g.E().hasLabel('self').has('weight',float(0.5)))], 
+    'g_mergeEXlabel_knows_out_marko_in_vadasX': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko').addV('person').property(T.id_,101).property('name','vadas')), (lambda g, xx1=None:g.mergeE(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko').out('knows').has('person','name','vadas'))], 
+    'g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b')), (lambda g, xx1=None:g.mergeE(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko').outE('knows').has('weight',float(0.5)).inV().has('person','name','vadas')), (lambda g, xx1=None:g.V().has('person','name','marko').outE('kno [...]
+    'g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X': [(lambda g, xx1=None:g.mergeE(xx1)), (lambda g, xx1=None:g.V()), (lambda g, xx1=None:g.E().hasLabel('knows').has('weight',float(0.5)))], 
+    'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX': [(lambda g, xx1=None,xx3=None,xx2=None:g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)), (lambda g, xx1=None,xx3=None,xx2=None:g.V()), (lambda g, xx1=None,xx3=None,xx2=None:g.E().hasLabel('knows').has('created','Y')), (lambda g, xx1=None,xx3=None,xx2=None:g.E().hasLabel('knows').has('created','N'))], 
+    'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists': [(lambda g, xx1=None,xx3=None,xx2=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b')), (lambda g, xx1=None,xx3=None,xx2=None:g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)), (lambda g, xx1=None,xx3=None,xx2=None:g.V()), (lambda g, xx1=No [...]
+    'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated': [(lambda g, xx1=None,xx3=None,xx2=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b').property('created','Y')), (lambda g, xx1=None,xx3=None,xx2=None:g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)), (lambda g, xx1=None,xx3=None,xx [...]
+    'g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated': [(lambda g, xx1=None,xx3=None,xx2=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b').property('created','Y').addE('knows').from_('a').to('b')), (lambda g, xx1=None,xx3=None,xx2=None:g.V().has('person','name','marko').mergeE(xx1).option(Merge. [...]
+    'g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko')), (lambda g, xx1=None:g.V(100).V(101).mergeE(xx1)), (lambda g, xx1=None:g.V()), (lambda g, xx1=None:g.E())], 
+    'g_injectXlabel_knows_out_marko_in_vadasX_mergeE': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko').addV('person').property(T.id_,101).property('name','vadas')), (lambda g, xx1=None:g.inject(xx1).mergeE()), (lambda g, xx1=None:g.V().has('person','name','marko').out('knows').has('person','name','vadas'))], 
+    'g_mergeVXlabel_person_name_stephenX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','stephen'))], 
+    'g_mergeVXlabel_person_name_markoX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko'))], 
+    'g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.mergeV(xx1).option(Merge.onCreate,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').has('age',19))], 
+    'g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.mergeV(xx1).option(Merge.onMatch,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))], 
+    'g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).mergeV(__.select('c')).option(Merge.onCreate,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').has('age',19))], 
+    'g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).mergeV(__.select('c')).option(Merge.onMatch,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))], 
+    'g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.mergeV(xx1).property('name','vadas','acl','public')), (lambda g, xx1=None:g.V().properties('name').hasValue('vadas').has('acl','public'))], 
+    'g_injectX0X_mergeVXlabel_person_name_stephenX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.inject(0).mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','stephen'))], 
+    'g_injectX0X_mergeVXlabel_person_name_markoX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.inject(0).mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko'))], 
+    'g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(0).mergeV(xx1).option(Merge.onCreate,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').has('age',19))], 
+    'g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(0).mergeV(xx1).option(Merge.onMatch,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))], 
+    'g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).inject(0).mergeV(__.select('c')).option(Merge.onCreate,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').ha [...]
+    'g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).inject(0).mergeV(__.select('c')).option(Merge.onMatch,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))], 
+    'g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.inject(0).mergeV(xx1).property('name','vadas','acl','public')), (lambda g, xx1=None:g.V().properties('name').hasValue('vadas').has('acl','public'))], 
+    'g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(xx1,xx2).mergeV(__.identity())), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen')), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko')), (lambda g, xx1=None,xx2=None:g.V())], 
+    'g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(xx1,xx2).mergeV()), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen')), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko')), (lambda g, xx1=None,xx2=None:g.V())], 
     'g_V_age_min': [(lambda g:g.V().age.min_())], 
     'g_V_foo_min': [(lambda g:g.V().foo.min_())], 
     'g_V_name_min': [(lambda g:g.V().name.min_())], 
@@ -780,7 +806,6 @@ world.gremlins = {
     'g_V_outXfollowedByX_group_byXsongTypeX_byXbothE_group_byXlabelX_byXweight_sumXX': [(lambda g:g.V().out('followedBy').group().by('songType').by(__.bothE().group().by(T.label).by(__.weight.sum_())))], 
     'g_V_groupXmX_byXnameX_byXinXknowsX_nameX_capXmX': [(lambda g:g.V().group('m').by('name').by(__.in_('knows').name).cap('m'))], 
     'g_V_group_byXlabelX_byXbothE_groupXaX_byXlabelX_byXweight_sumX_weight_sumX': [(lambda g:g.V().group().by(T.label).by(__.bothE().group('a').by(T.label).by(__.weight.sum_()).weight.sum_()))], 
-    'g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX': [(lambda g, xx1=None:g.withSideEffect('a',xx1).V().group('a').by('name').by(__.outE().label().fold()).cap('a').unfold().group().by(Column.keys).by(__.select(Column.values).order(Scope.local).by(Order.asc)))], 
     'g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX': [(lambda g:g.V().hasLabel('person').as_('p').out('created').group().by('name').by(__.select('p').age.sum_()))], 
     'g_V_hasLabelXpersonX_asXpX_outXcreatedX_groupXaX_byXnameX_byXselectXpX_valuesXageX_sumX_capXaX': [(lambda g:g.V().hasLabel('person').as_('p').out('created').group('a').by('name').by(__.select('p').age.sum_()).cap('a'))], 
     'g_V_group_byXlabelX_byXlabel_countX': [(lambda g:g.V().group().by(__.label()).by(__.label().count()))], 
@@ -803,7 +828,7 @@ world.gremlins = {
     'g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX': [(lambda g:g.V().has('person','name','marko').both('knows').groupCount().by(__.name.fold()))], 
     'g_VX1X_out_injectXv2X_name': [(lambda g, vid1=None,v2=None:g.V(vid1).out().inject(v2).name)], 
     'g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path': [(lambda g, l1=None,vid1=None:g.V(vid1).out().name.inject('daniel').as_('a').map(l1).path())], 
-    'g_VX1X_injectXg_VX4XX_out_name': [(lambda g, vid1=None,v4=None:g.V(vid1).inject(v4).out().name)], 
+    'g_VX1X_injectXg_VX4XX_out_name': [(lambda g, vid1=None,v2=None:g.V(vid1).inject(v2).out().name)], 
     'g_injectXnull_1_3_nullX': [(lambda g:g.inject(None,1,3,None))], 
     'g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX': [(lambda g:g.inject(10,20,None,20,10,10).groupCount('x').dedup().as_('y').project('a','b').by().by(__.select('x').select(__.select('y'))))], 
     'g_injectXname_marko_age_nullX_selectXname_ageX': [(lambda g, xx1=None:g.inject(xx1).select('name','age'))], 
diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
index c931bc9..14b9577 100644
--- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
+++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
@@ -44,6 +44,7 @@ class TestDriverRemoteConnection(object):
         assert long(6) == g.V().count().toList()[0]
         # #
         assert Vertex(1) == g.V(1).next()
+        assert Vertex(1) == g.V(Vertex(1)).next()
         assert 1 == g.V(1).id_().next()
         assert Traverser(Vertex(1)) == g.V(1).nextTraverser()
         assert 1 == len(g.V(1).toList())
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py b/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py
index 2f2b211..b12c48a 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py
@@ -24,7 +24,7 @@ import math
 from gremlin_python.statics import timestamp, long, SingleByte, SingleChar, ByteBufferType
 from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path
 from gremlin_python.structure.io.graphbinaryV1 import GraphBinaryWriter, GraphBinaryReader
-from gremlin_python.process.traversal import Barrier, Binding, Bytecode
+from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Merge
 
 
 class TestGraphBinaryReader(object):
@@ -166,6 +166,11 @@ class TestGraphSONWriter(object):
         output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
         assert x == output
 
+    def test_merge(self):
+        x = Merge.onMatch
+        output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
+        assert x == output
+
     def test_binding(self):
         x = Binding("name", "marko")
         output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java
index b05ae92..3af49f4 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java
@@ -76,6 +76,7 @@ public class WsGremlinBinaryRequestDecoder extends MessageToMessageDecoder<Binar
             try {
                 objects.add(serializer.deserializeRequest(messageBytes.discardReadBytes()));
             } catch (SerializationException se) {
+                logger.warn(se.getMessage());
                 objects.add(RequestMessage.INVALID);
             }
         } finally {
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java
index 50dc6f3..84b0888 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java
@@ -63,6 +63,7 @@ public class WsGremlinTextRequestDecoder extends MessageToMessageDecoder<TextWeb
 
             objects.add(serializer.deserializeRequest(frame.text()));
         } catch (SerializationException se) {
+            logger.warn(se.getMessage());
             objects.add(RequestMessage.INVALID);
         }
     }
diff --git a/gremlin-test/features/map/MergeEdge.feature b/gremlin-test/features/map/MergeEdge.feature
new file mode 100644
index 0000000..a04ca40
--- /dev/null
+++ b/gremlin-test/features/map/MergeEdge.feature
@@ -0,0 +1,201 @@
+# 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 @StepMergeE
+Feature: Step - mergeE()
+
+  Scenario: g_V_mergeEXlabel_self_weight_05X
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29)
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"self\", \"weight\":\"d[0.5].d\"}]"
+    And the traversal of
+      """
+      g.V().mergeE(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.E().hasLabel(\"self\").has(\"weight\",0.5)"
+
+  @AllowUserSuppliedIds
+  Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko").
+        addV("person").property(T.id, 101).property("name", "vadas")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+    And the traversal of
+      """
+      g.mergeE(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").out(\"knows\").has(\"person\",\"name\",\"vadas\")"
+
+  @AllowUserSuppliedIds
+  Scenario: g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+        addV("person").property(T.id, 101).property("name", "vadas").as("b").
+        addE("knows").from("a").to("b")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\", \"weight\":\"d[0.5].d\"}]"
+    And the traversal of
+      """
+      g.mergeE(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").outE(\"knows\").has(\"weight\",0.5).inV().has(\"person\",\"name\",\"vadas\")"
+    And the graph should return 2 for count of "g.V().has(\"person\",\"name\",\"marko\").outE(\"knows\").inV().has(\"person\",\"name\",\"vadas\")"
+
+  @AllowUserSuppliedIds
+  Scenario: g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\", \"weight\":\"d[0.5].d\"}]"
+    And the traversal of
+      """
+      g.mergeE(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 1 for count of "g.E().hasLabel(\"knows\").has(\"weight\",0.5)"
+
+  @AllowUserSuppliedIds
+  Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\", \"created\":\"Y\"}]"
+    And using the parameter xx3 defined as "m[{\"t[label]\": \"knows\", \"created\":\"N\"}]"
+    And the traversal of
+      """
+      g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 1 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+    And the graph should return 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+  @AllowUserSuppliedIds
+  Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+        addV("person").property(T.id, 101).property("name", "vadas").as("b").
+        addE("knows").from("a").to("b")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\",\"created\":\"Y\"}]"
+    And using the parameter xx3 defined as "m[{\"created\":\"N\"}]"
+    And the traversal of
+      """
+      g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+    And the graph should return 1 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+  @AllowUserSuppliedIds
+  Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+        addV("person").property(T.id, 101).property("name", "vadas").as("b").
+        addE("knows").from("a").to("b").property("created","Y")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\",\"created\":\"Y\"}]"
+    And using the parameter xx3 defined as "m[{\"created\":\"N\"}]"
+    And the traversal of
+      """
+      g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+    And the graph should return 1 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+  @AllowUserSuppliedIds
+  Scenario: g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+        addV("person").property(T.id, 101).property("name", "vadas").as("b").
+        addE("knows").from("a").to("b").property("created","Y").
+        addE("knows").from("a").to("b")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\",\"created\":\"Y\"}]"
+    And using the parameter xx3 defined as "m[{\"created\":\"N\"}]"
+    And the traversal of
+      """
+      g.V().has("person","name","marko").mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+      """
+    When iterated to list
+    Then the result should have a count of 2
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 2 for count of "g.E()"
+    And the graph should return 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+    And the graph should return 2 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+  @AllowUserSuppliedIds
+  Scenario: g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+    And the traversal of
+      """
+      g.V(100).V(101).mergeE(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 0
+    And the graph should return 1 for count of "g.V()"
+    And the graph should return 0 for count of "g.E()"
+
+  @AllowUserSuppliedIds
+  Scenario: g_injectXlabel_knows_out_marko_in_vadasX_mergeE
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 100).property("name", "marko").
+        addV("person").property(T.id, 101).property("name", "vadas")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+    And the traversal of
+      """
+      g.inject(xx1).mergeE()
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").out(\"knows\").has(\"person\",\"name\",\"vadas\")"
\ No newline at end of file
diff --git a/gremlin-test/features/map/MergeVertex.feature b/gremlin-test/features/map/MergeVertex.feature
new file mode 100644
index 0000000..cda2e42
--- /dev/null
+++ b/gremlin-test/features/map/MergeVertex.feature
@@ -0,0 +1,283 @@
+# 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 @StepMergeV
+Feature: Step - mergeV()
+
+  Scenario: g_mergeVXlabel_person_name_stephenX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+
+  Scenario: g_mergeVXlabel_person_name_markoX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+
+  Scenario: g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1).option(Merge.onCreate,xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+  Scenario: g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1).option(Merge.onMatch,xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+  Scenario: g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.withSideEffect("c", xx1).
+        withSideEffect("m", xx2).
+        mergeV(__.select("c")).option(Merge.onCreate, __.select("m"))
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+  Scenario: g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.withSideEffect("c", xx1).
+        withSideEffect("m", xx2).
+        mergeV(__.select("c")).option(Merge.onMatch, __.select("m"))
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1).property("name","vadas","acl","public")
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().properties(\"name\").hasValue(\"vadas\").has(\"acl\",\"public\")"
+
+  Scenario: g_injectX0X_mergeVXlabel_person_name_stephenX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And the traversal of
+      """
+      g.inject(0).mergeV(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+
+  Scenario: g_injectX0X_mergeVXlabel_person_name_markoX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And the traversal of
+      """
+      g.inject(0).mergeV(xx1)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+
+  Scenario: g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.inject(0).mergeV(xx1).option(Merge.onCreate,xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+  Scenario: g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.inject(0).mergeV(xx1).option(Merge.onMatch,xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+  Scenario: g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.withSideEffect("c", xx1).
+        withSideEffect("m", xx2).
+        inject(0).mergeV(__.select("c")).option(Merge.onCreate, __.select("m"))
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+  Scenario: g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+    And the traversal of
+      """
+      g.withSideEffect("c", xx1).
+        withSideEffect("m", xx2).
+        inject(0).mergeV(__.select("c")).option(Merge.onMatch, __.select("m"))
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+  @MultiMetaProperties
+  Scenario: g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And the traversal of
+      """
+      g.inject(0).mergeV(xx1).property("name","vadas","acl","public")
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().properties(\"name\").hasValue(\"vadas\").has(\"acl\",\"public\")"
+
+  Scenario: g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And the traversal of
+      """
+      g.inject(xx1, xx2).mergeV(__.identity())
+      """
+    When iterated to list
+    Then the result should have a count of 2
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+    And the graph should return 2 for count of "g.V()"
+
+  Scenario: g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29).as("marko")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+    And the traversal of
+      """
+      g.inject(xx1, xx2).mergeV()
+      """
+    When iterated to list
+    Then the result should have a count of 2
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+    And the graph should return 2 for count of "g.V()"
\ No newline at end of file
diff --git a/gremlin-test/features/sideEffect/Group.feature b/gremlin-test/features/sideEffect/Group.feature
index d4d3867..01a5a21 100644
--- a/gremlin-test/features/sideEffect/Group.feature
+++ b/gremlin-test/features/sideEffect/Group.feature
@@ -264,17 +264,17 @@ Feature: Step - group()
       | m[{"software":"d[2.0].d", "person":"d[5.0].d"}] |
 
   # The post-ordering really isn't really right but works around TINKERPOP-2600
-  Scenario: g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX
-    Given the modern graph
-    And using the parameter xx1 defined as "m[{\"marko\":[\"666\"], \"noone\":[\"blah\"]}]"
-    And the traversal of
-      """
-      g.withSideEffect("a", xx1).V().group("a").by("name").by(__.outE().label().fold()).cap("a").unfold().group().by(keys).by(select(values).order(Scope.local).by(Order.asc))
-      """
-    When iterated to list
-    Then the result should be unordered
-      | result |
-      | m[{"ripple":[], "peter":["created"], "noone":["blah"], "vadas":[], "josh":["created", "created"], "lop":[], "marko":["666", "created", "knows", "knows"]}] |
+#  Scenario: g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX
+#    Given the modern graph
+#    And using the parameter xx1 defined as "m[{\"marko\":\"l[\"666\"]\", \"noone\":\"l[\"blah\"]\"}]"
+#    And the traversal of
+#      """
+#      g.withSideEffect("a", xx1).V().group("a").by("name").by(__.outE().label().fold()).cap("a").unfold().group().by(Column.keys).by(select(Column.values).order(Scope.local).by(Order.asc))
+#      """
+#    When iterated to list
+#    Then the result should be unordered
+#      | result |
+#      | m[{"ripple":[], "peter":["created"], "noone":["blah"], "vadas":[], "josh":["created", "created"], "lop":[], "marko":["666", "created", "knows", "knows"]}] |
 
   @GraphComputerVerificationStarGraphExceeded
   Scenario: g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index a186095..75edc15 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -51,13 +51,14 @@ Feature: Step - inject()
       | p[v[marko],v[vadas],vadas,d[5].i] |
       | p[v[marko],v[josh],josh,d[4].i] |
 
+  @GraphComputerVerificationInjectionNotSupported
   Scenario: g_VX1X_injectXg_VX4XX_out_name
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
-    And using the parameter v4 defined as "v[josh]"
+    And using the parameter v2 defined as "v[josh]"
     And the traversal of
       """
-      g.V(vid1).inject(v4).out().values("name")
+      g.V(vid1).inject(v2).out().values("name")
       """
     When iterated to list
     Then the result should be unordered
@@ -130,7 +131,6 @@ Feature: Step - inject()
       | result |
       | null |
 
-
   Scenario: g_inject
     Given the empty graph
     And the traversal of
@@ -140,7 +140,6 @@ Feature: Step - inject()
     When iterated to list
     Then the result should be empty
 
-  @GraphComputerVerificationInjectionNotSupported
   Scenario: g_VX1X_valuesXageX_injectXnull_nullX
     Given the modern graph
     And using the parameter xx1 defined as "v[marko].id"
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
index b8021c1..d896228 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
@@ -38,6 +38,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSo
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
@@ -64,10 +65,12 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -88,10 +91,21 @@ public final class StepDefinition {
     private static final Pattern edgeTripletPattern = Pattern.compile("(.+)-(.+)->(.+)");
     private static final Pattern ioPattern = Pattern.compile("g\\.io\\(\"(.*)\"\\).*");
     private List<Pair<Pattern, Function<String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, Function<String,String>>>() {{
-        // expects json so that should port to the Gremlin script form - replace curly json braces with square ones
-        // for Gremlin sake.
-        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), s -> s.replace('{','[').replace('}', ']')));
-
+        // expects json so that should port to the Gremlin script form
+        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), s -> {
+            // can't handled embedded maps because of the string replace below on the curly braces
+            final String[] items = s.replace("{", "").replace("}", "").split(",");
+            final String listItems = Stream.of(items).map(String::trim).map(x -> {
+                final String[] pair = x.split(":");
+
+                // if wrapping double quotes aren't removed they end up re-wrapping again for pure string values
+                // on conversion
+                final String convertedKey = convertToString(pair[0].trim().replace("\"", ""));
+                final String convertedValue = convertToString(pair[1].trim().replace("\"", ""));
+                return String.format("%s:%s", convertedKey, convertedValue);
+            }).collect(Collectors.joining(","));
+            return String.format("[%s]", listItems);
+        }));
         add(Pair.with(Pattern.compile("l\\[\\]"), s -> "[]"));
         add(Pair.with(Pattern.compile("l\\[(.*)\\]"), s -> {
             final String[] items = s.split(",");
@@ -109,9 +123,12 @@ public final class StepDefinition {
 
         add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.id"), s -> g.V().has("name", s).id().next().toString()));
         add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.sid"), s -> g.V().has("name", s).id().next().toString()));
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> {
+            final Iterator<Object> itty = g.V().has("name", s).id();
+            return String.format("new Vertex(%s,\"%s\")", itty.hasNext() ? itty.next() : s, Vertex.DEFAULT_LABEL);
+        }));
         add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.id"), s -> getEdgeIdString(g, s)));
         add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.sid"), s -> getEdgeIdString(g, s)));
-
         add(Pair.with(Pattern.compile("t\\[(.*)\\]"), s -> String.format("T.%s", s)));
         add(Pair.with(Pattern.compile("D\\[(.*)\\]"), s -> String.format("Direction.%s", s)));
 
@@ -121,9 +138,6 @@ public final class StepDefinition {
         add(Pair.with(Pattern.compile("c\\[(.*)\\]"), s -> {
             throw new AssumptionViolatedException("This test uses a lambda as a parameter which is not supported by gremlin-language");
         }));
-        add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> {
-            throw new AssumptionViolatedException("This test uses a Vertex as a parameter which is not supported by gremlin-language");
-        }));
         add(Pair.with(Pattern.compile("e\\[(.+)\\]"), s -> {
             throw new AssumptionViolatedException("This test uses a Edge as a parameter which is not supported by gremlin-language");
         }));
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
index e9c4602..a785724 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
@@ -24,64 +24,11 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.OrderabilityTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.BranchTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.ChooseTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.OptionalTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.RepeatTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.CoinTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.CyclicPathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.IsTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SampleTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SimplePathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TailTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.CoalesceTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ConstantTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ElementMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.FlatMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.IndexTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MapTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProfileTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProjectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.UnfoldTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ValueMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.WriteTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AggregateTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.ExplainTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupCountTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SackTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectCapTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.StoreTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SubgraphTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.TreeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest;
@@ -93,8 +40,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.Transl
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.IncidentToAdjacentStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategyProcessTest;
-import org.apache.tinkerpop.gremlin.structure.Graph;
-import org.apache.tinkerpop.gremlin.structure.StructureStandardSuite;
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.RunnerBuilder;
 
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java
index ed4b2e7..642cda4 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java
@@ -40,8 +40,8 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.identi
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.label;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.any;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.none;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.any;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.none;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java
index 28f3abe..d1f4933 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java
@@ -22,9 +22,9 @@ import org.apache.tinkerpop.gremlin.LoadGraphWith;
 import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
 import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 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.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.MapHelper;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.junit.Test;
@@ -195,7 +195,7 @@ public abstract class ChooseTest extends AbstractGremlinProcessTest {
             return g.V().choose(label())
                     .option("blah", out("knows"))
                     .option("bleep", out("created"))
-                    .option(TraversalOptionParent.Pick.none, identity()).values("name");
+                    .option(Pick.none, identity()).values("name");
         }
 
         @Override
@@ -212,7 +212,7 @@ public abstract class ChooseTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, Map<String, Long>> get_g_V_hasLabelXpersonX_chooseXageX__optionX27L__constantXyoungXX_optionXnone__constantXoldXX_groupCount() {
             return g.V().hasLabel("person").choose(values("age"))
                     .option(27L, __.constant("young"))
-                    .option(TraversalOptionParent.Pick.none, __.constant("old"))
+                    .option(Pick.none, __.constant("old"))
                     .groupCount();
         }
 
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
index 5aa9823..e6c6ebc 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
@@ -21,11 +21,13 @@ package org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration;
 import org.apache.tinkerpop.gremlin.FeatureRequirement;
 import org.apache.tinkerpop.gremlin.FeatureRequirementSet;
 import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.MutationListener;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
@@ -40,7 +42,9 @@ import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
@@ -83,6 +87,31 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
 
     @Test
     @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+    public void shouldTriggerAddVertexViaMergeV() {
+        final StubMutationListener listener1 = new StubMutationListener();
+        final StubMutationListener listener2 = new StubMutationListener();
+        final EventStrategy.Builder builder = EventStrategy.build()
+                .addListener(listener1)
+                .addListener(listener2);
+
+        if (graph.features().graph().supportsTransactions())
+            builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+        final EventStrategy eventStrategy = builder.create();
+
+        graph.addVertex("some", "thing");
+        final GraphTraversalSource gts = create(eventStrategy);
+        final Map<Object,Object> m = new HashMap<>();
+        m.put("any", "thing");
+        gts.V().mergeV(m).property("any", "thing").next();
+
+        tryCommit(graph, g -> assertEquals(1, IteratorUtils.count(gts.V().has("any", "thing"))));
+        assertEquals(1, listener1.addVertexEventRecorded());
+        assertEquals(1, listener2.addVertexEventRecorded());
+    }
+
+    @Test
+    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
     public void shouldTriggerAddVertexFromStart() {
         final StubMutationListener listener1 = new StubMutationListener();
         final StubMutationListener listener2 = new StubMutationListener();
@@ -135,6 +164,37 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
 
     @Test
     @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
+    public void shouldTriggerAddEdgeViaMergeE() {
+        final StubMutationListener listener1 = new StubMutationListener();
+        final StubMutationListener listener2 = new StubMutationListener();
+        final EventStrategy.Builder builder = EventStrategy.build()
+                .addListener(listener1)
+                .addListener(listener2);
+
+        if (graph.features().graph().supportsTransactions())
+            builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+        final EventStrategy eventStrategy = builder.create();
+
+        final Vertex v = graph.addVertex();
+        v.addEdge("self", v);
+
+        final GraphTraversalSource gts = create(eventStrategy);
+        final Map<Object,Object> m = new HashMap<>();
+        m.put(T.label, "self-but-different");
+        gts.V(v).mergeE(m).next();
+
+        tryCommit(graph, g -> assertEquals(2, IteratorUtils.count(gts.E())));
+
+        assertEquals(0, listener1.addVertexEventRecorded());
+        assertEquals(0, listener2.addVertexEventRecorded());
+
+        assertEquals(1, listener1.addEdgeEventRecorded());
+        assertEquals(1, listener2.addEdgeEventRecorded());
+    }
+
+    @Test
+    @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
     public void shouldTriggerAddEdgeByPath() {
         final StubMutationListener listener1 = new StubMutationListener();
         final StubMutationListener listener2 = new StubMutationListener();
@@ -246,6 +306,40 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
 
     @Test
     @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+    public void shouldTriggerAddVertexPropertyChangedViaMergeV() {
+        final StubMutationListener listener1 = new StubMutationListener();
+        final StubMutationListener listener2 = new StubMutationListener();
+        final EventStrategy.Builder builder = EventStrategy.build()
+                .addListener(listener1)
+                .addListener(listener2);
+
+        if (graph.features().graph().supportsTransactions())
+            builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+        final EventStrategy eventStrategy = builder.create();
+
+        final Vertex vSome = graph.addVertex("some", "thing");
+        vSome.property(VertexProperty.Cardinality.single, "that", "thing");
+        final GraphTraversalSource gts = create(eventStrategy);
+
+        final Map<Object,Object> m1 = new HashMap<>();
+        m1.put("any", "thing");
+        gts.mergeV(m1).iterate();
+
+        final Map<Object,Object> m2 = new HashMap<>();
+        m2.put("any", "thing else");
+        gts.mergeV(m1).option(Merge.onMatch, m2).iterate();
+
+        tryCommit(graph, g -> assertEquals(1, IteratorUtils.count(gts.V().has("any", "thing else"))));
+
+        assertEquals(1, listener1.addVertexEventRecorded());
+        assertEquals(1, listener2.addVertexEventRecorded());
+        assertEquals(1, listener2.vertexPropertyChangedEventRecorded());
+        assertEquals(1, listener1.vertexPropertyChangedEventRecorded());
+    }
+
+    @Test
+    @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
     public void shouldTriggerAddVertexPropertyPropertyChanged() {
         final StubMutationListener listener1 = new StubMutationListener();
@@ -307,6 +401,44 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
 
     @Test
     @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
+    public void shouldTriggerUpdateEdgePropertyAddedViaMergeE() {
+        final StubMutationListener listener1 = new StubMutationListener();
+        final StubMutationListener listener2 = new StubMutationListener();
+        final EventStrategy.Builder builder = EventStrategy.build()
+                .addListener(listener1)
+                .addListener(listener2);
+
+        if (graph.features().graph().supportsTransactions())
+            builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+        final EventStrategy eventStrategy = builder.create();
+
+        final Vertex v = graph.addVertex();
+        v.addEdge("self", v);
+
+        final GraphTraversalSource gts = create(eventStrategy);
+        final Map<Object,Object> m = new HashMap<>();
+        m.put(T.label, "self");
+        final Map<Object,Object> mMatch = new HashMap<>();
+        mMatch.put("some", "thing");
+        gts.V(v).mergeE(m).option(Merge.onMatch, mMatch).next();
+
+        tryCommit(graph, g -> assertEquals(1, IteratorUtils.count(gts.E().has("some", "thing"))));
+
+        assertEquals(1, IteratorUtils.count(gts.E()));
+
+        assertEquals(0, listener1.addVertexEventRecorded());
+        assertEquals(0, listener2.addVertexEventRecorded());
+
+        assertEquals(0, listener1.addEdgeEventRecorded());
+        assertEquals(0, listener2.addEdgeEventRecorded());
+
+        assertEquals(1, listener2.edgePropertyChangedEventRecorded());
+        assertEquals(1, listener1.edgePropertyChangedEventRecorded());
+    }
+
+    @Test
+    @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
     public void shouldTriggerEdgePropertyChanged() {
         final StubMutationListener listener1 = new StubMutationListener();
         final StubMutationListener listener2 = new StubMutationListener();
diff --git a/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java b/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java
index 87e5f86..98f3f35 100644
--- a/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java
+++ b/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java
@@ -26,12 +26,12 @@ import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
 import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics;
@@ -178,7 +178,7 @@ public class Model {
         addGraphProcessEntry(Direction.OUT, "Direction", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
         addGraphProcessEntry(Operator.sum, "Operator", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
         addGraphProcessEntry(Order.shuffle, "Order", "", before3_5_0);
-        addGraphProcessEntry(TraversalOptionParent.Pick.any, "Pick", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
+        addGraphProcessEntry(Pick.any, "Pick", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
         addGraphProcessEntry(Pop.all, "Pop", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
         addGraphProcessEntry(org.apache.tinkerpop.gremlin.util.function.Lambda.function("{ it.get() }"), "Lambda", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
         final TraversalMetrics tm = createStaticTraversalMetrics();
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy
index 878f6a3..3806884 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy
@@ -22,16 +22,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetri
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
 
 import java.time.*
-import java.nio.file.*
+
 import org.apache.tinkerpop.gremlin.driver.ser.*
 import org.apache.tinkerpop.gremlin.process.traversal.*
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
 import org.apache.tinkerpop.gremlin.structure.*
 import org.apache.tinkerpop.gremlin.structure.io.graphson.*
 import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
-import org.apache.tinkerpop.gremlin.structure.io.gryo.*
 import org.apache.commons.configuration.BaseConfiguration
 
 import java.util.concurrent.TimeUnit
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy
index e1ff14d..6b497b7 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy
@@ -22,16 +22,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetri
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
 
 import java.time.*
-import java.nio.file.*
+
 import org.apache.tinkerpop.gremlin.driver.ser.*
 import org.apache.tinkerpop.gremlin.process.traversal.*
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
 import org.apache.tinkerpop.gremlin.structure.*
 import org.apache.tinkerpop.gremlin.structure.io.graphson.*
 import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
-import org.apache.tinkerpop.gremlin.structure.io.gryo.*
 import org.apache.commons.configuration.BaseConfiguration
 
 import java.util.concurrent.TimeUnit
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy
index d231aa7..4f0dfac 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy
@@ -21,19 +21,13 @@
 import org.apache.commons.configuration.BaseConfiguration
 import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics
 import org.apache.tinkerpop.shaded.kryo.io.Output
 
 import java.time.*
-import java.nio.file.*
-import org.apache.tinkerpop.gremlin.driver.ser.*
+
 import org.apache.tinkerpop.gremlin.process.traversal.*
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
 import org.apache.tinkerpop.gremlin.structure.*
-import org.apache.tinkerpop.gremlin.structure.io.graphson.*
-import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
 import org.apache.tinkerpop.gremlin.structure.io.gryo.*
 
 import java.util.concurrent.TimeUnit
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy
index 6e66f36..d67eec4 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy
@@ -21,19 +21,13 @@
 import org.apache.commons.configuration.BaseConfiguration
 import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics
 import org.apache.tinkerpop.shaded.kryo.io.Output
 
 import java.time.*
-import java.nio.file.*
-import org.apache.tinkerpop.gremlin.driver.ser.*
+
 import org.apache.tinkerpop.gremlin.process.traversal.*
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
 import org.apache.tinkerpop.gremlin.structure.*
-import org.apache.tinkerpop.gremlin.structure.io.graphson.*
-import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
 import org.apache.tinkerpop.gremlin.structure.io.gryo.*
 
 import java.util.concurrent.TimeUnit
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeEdgeStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeEdgeStep.java
new file mode 100644
index 0000000..23b49a8
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeEdgeStep.java
@@ -0,0 +1,97 @@
+/*
+ * 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.tinkergraph.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Optimizes {@code mergeE()} searches by attempting to use an index where possible.
+ */
+public class TinkerMergeEdgeStep<S> extends MergeEdgeStep<S> {
+
+    public TinkerMergeEdgeStep(final MergeEdgeStep step) {
+        super(step.getTraversal(), step.isStart(), step.getSearchCreateTraversal());
+        if (step.getOnMatchTraversal() != null) this.addChildOption(Merge.onMatch, step.getOnMatchTraversal());
+        if (step.getOnCreateTraversal() != null) this.addChildOption(Merge.onCreate, step.getOnCreateTraversal());
+        if (step.getCallbackRegistry() != null) this.callbackRegistry = step.getCallbackRegistry();
+    }
+
+    @Override
+    protected Stream<Edge> createSearchStream(final Map<Object, Object> search) {
+        final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+        Optional<String> firstIndex = Optional.empty();
+
+        Stream<Edge> stream;
+        // prioritize lookup by id but otherwise attempt an index lookup
+        if (search.containsKey(T.id)) {
+            stream = IteratorUtils.stream(graph.edges(search.get(T.id)));
+        } else {
+            // look for the first index we can find - that's the lucky winner. may or may not be the most selective
+            final Set<String> indexedKeys = graph.getIndexedKeys(Edge.class);
+            firstIndex = search.keySet().stream().
+                    filter(k -> k instanceof String).
+                    map(k -> (String) k).
+                    filter(indexedKeys::contains).findFirst();
+
+            // use the index if possible otherwise just in memory filter
+            stream = firstIndex.map(s -> TinkerHelper.queryEdgeIndex(graph, s, search.get(s)).stream().map(e -> (Edge) e)).
+                    orElseGet(() -> IteratorUtils.stream(graph.edges()));
+        }
+
+        final Optional<String> indexUsed = firstIndex;
+        stream = stream.filter(e -> {
+            // try to match on all search criteria skipping T.id as it was handled above
+            return search.entrySet().stream().filter(kv -> {
+                final Object k = kv.getKey();
+                return k != T.id && !(indexUsed.isPresent() && indexUsed.get().equals(k));
+            }).allMatch(kv -> {
+                if (kv.getKey() == T.label) {
+                    return e.label().equals(kv.getValue());
+                } else if (kv.getKey() instanceof Direction) {
+                    final Direction direction = (Direction) kv.getKey();
+
+                    // try to take advantage of string id conversions of the graph by doing a lookup rather
+                    // than direct compare on id
+                    final Iterator<Vertex> found = graph.vertices(kv.getValue());
+                    return found.hasNext() && e.vertices(direction).next().equals(found.next());
+                } else {
+                    final Property<Object> vp = e.property(kv.getKey().toString());
+                    return vp.isPresent() && kv.getValue().equals(vp.value());
+                }
+            });
+        });
+
+        return stream;
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java
new file mode 100644
index 0000000..6771ebd
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java
@@ -0,0 +1,88 @@
+/*
+ * 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.tinkergraph.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Optimizes {@code mergeV()} searches by attempting to use an index where possible.
+ */
+public class TinkerMergeVertexStep<S> extends MergeVertexStep<S> {
+    public TinkerMergeVertexStep(final MergeVertexStep step) {
+        super(step.getTraversal(), step.isStart(), step.getSearchCreateTraversal());
+        if (step.getOnMatchTraversal() != null) this.addChildOption(Merge.onMatch, step.getOnMatchTraversal());
+        if (step.getOnCreateTraversal() != null) this.addChildOption(Merge.onCreate, step.getOnCreateTraversal());
+        if (step.getCallbackRegistry() != null) this.callbackRegistry = step.getCallbackRegistry();
+    }
+
+    @Override
+    protected Stream<Vertex> createSearchStream(final Map<Object, Object> search) {
+        final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+        Optional<String> firstIndex = Optional.empty();
+
+        Stream<Vertex> stream;
+        // prioritize lookup by id but otherwise attempt an index lookup
+        if (search.containsKey(T.id)) {
+            stream = IteratorUtils.stream(graph.vertices(search.get(T.id)));
+        } else {
+            // look for the first index we can find - that's the lucky winner. may or may not be the most selective
+            final Set<String> indexedKeys = graph.getIndexedKeys(Vertex.class);
+            firstIndex = search.keySet().stream().
+                    filter(k -> k instanceof String).
+                    map(k -> (String) k).
+                    filter(indexedKeys::contains).findFirst();
+
+            // use the index if possible otherwise just in memory filter
+            stream = firstIndex.map(s -> TinkerHelper.queryVertexIndex(graph, s, search.get(s)).stream().map(v -> (Vertex) v)).
+                    orElseGet(() -> IteratorUtils.stream(graph.vertices()));
+        }
+
+        final Optional<String> indexUsed = firstIndex;
+        stream = stream.filter(v -> {
+            // try to match on all search criteria skipping T.id as it was handled above
+            return search.entrySet().stream().filter(kv -> {
+                final Object k = kv.getKey();
+                return k != T.id && !(indexUsed.isPresent() && indexUsed.get().equals(k));
+            }).allMatch(kv -> {
+                if (kv.getKey() == T.label) {
+                    return v.label().equals(kv.getValue());
+                } else {
+                    final VertexProperty<Object> vp = v.property(kv.getKey().toString());
+                    return vp.isPresent() && kv.getValue().equals(vp.value());
+                }
+            });
+        });
+
+        return stream;
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeEVStepStrategy.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeEVStepStrategy.java
new file mode 100644
index 0000000..c4b1ea5
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeEVStepStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * 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.tinkergraph.process.traversal.strategy.optimization;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map.TinkerMergeEdgeStep;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map.TinkerMergeVertexStep;
+
+/**
+ * Optimizes {@code mergeV()} and {@code mergeE()} search lookups by using {@link TinkerMergeVertexStep} and
+ * {@link TinkerMergeEdgeStep} respectively.
+ */
+public final class TinkerMergeEVStepStrategy extends AbstractTraversalStrategy<TraversalStrategy.ProviderOptimizationStrategy>
+        implements TraversalStrategy.ProviderOptimizationStrategy {
+
+    private static final TinkerMergeEVStepStrategy INSTANCE = new TinkerMergeEVStepStrategy();
+
+    private TinkerMergeEVStepStrategy() {
+    }
+
+    @Override
+    public void apply(final Traversal.Admin<?, ?> traversal) {
+        if (TraversalHelper.onGraphComputer(traversal))
+            return;
+
+        for (final MergeVertexStep originalMergeVertexStep : TraversalHelper.getStepsOfClass(MergeVertexStep.class, traversal)) {
+            final TinkerMergeVertexStep tinkerMergeVertexStep = new TinkerMergeVertexStep(originalMergeVertexStep);
+            TraversalHelper.replaceStep(originalMergeVertexStep, tinkerMergeVertexStep, traversal);
+        }
+
+        for (final MergeEdgeStep originalMergeEdgeStep : TraversalHelper.getStepsOfClass(MergeEdgeStep.class, traversal)) {
+            final TinkerMergeEdgeStep tinkerMergeEdgeStep = new TinkerMergeEdgeStep(originalMergeEdgeStep);
+            TraversalHelper.replaceStep(originalMergeEdgeStep, tinkerMergeEdgeStep, traversal);
+        }
+    }
+
+    public static TinkerMergeEVStepStrategy instance() {
+        return INSTANCE;
+    }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index b9dda16..795b4fa 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -39,6 +39,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
 import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy;
 import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerMergeEVStepStrategy;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 import java.io.File;
@@ -72,7 +73,8 @@ public final class TinkerGraph implements Graph {
     static {
         TraversalStrategies.GlobalCache.registerStrategies(TinkerGraph.class, TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().addStrategies(
                 TinkerGraphStepStrategy.instance(),
-                TinkerGraphCountStrategy.instance()));
+                TinkerGraphCountStrategy.instance(),
+                TinkerMergeEVStepStrategy.instance()));
     }
 
     private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
index 7d6f820..46856f8 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
@@ -106,6 +106,7 @@ public class TinkerGraphWorld implements World {
 
         private static final List<String> TAGS_TO_IGNORE = Arrays.asList(
                 "@StepDrop",
+                "@StepInject",
                 "@StepV",
                 "@GraphComputerVerificationOneBulk",
                 "@GraphComputerVerificationStrategyNotSupported",