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 2023/06/21 19:43:26 UTC

[tinkerpop] branch TINKERPOP-2957 created (now df1297b12a)

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

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


      at df1297b12a TINKERPOP-2957 allow cardinality to be specified for mergeV()

This branch includes the following new commits:

     new e0036006d9 wip
     new df1297b12a TINKERPOP-2957 allow cardinality to be specified for mergeV()

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[tinkerpop] 01/02: wip

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e0036006d9724412f2393ff26f25c15a94e1aa83
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Mon Jun 19 11:27:12 2023 -0400

    wip
---
 .../grammar/DefaultGremlinBaseVisitor.java         |  5 ++
 .../language/grammar/GenericLiteralVisitor.java    | 19 +++++
 .../process/traversal/CardinalityValue.java        | 81 ++++++++++++++++++++++
 .../grammar/GeneralLiteralVisitorTest.java         | 34 +++++++++
 .../process/traversal/CardinalityValueTest.java    | 64 +++++++++++++++++
 gremlin-language/src/main/antlr4/Gremlin.g4        |  7 ++
 6 files changed, 210 insertions(+)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index 39dac7efa5..e24640eeb1 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
@@ -1432,4 +1432,9 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im
 	 * {@inheritDoc}
 	 */
 	@Override public T visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) { notImplemented(ctx); return null; }
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalCardinalityValue(final GremlinParser.TraversalCardinalityValueContext ctx) { notImplemented(ctx); return null; }
 }
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 78d144616f..7e604efe80 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
@@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.TerminalNode;
 import org.apache.commons.text.StringEscapeUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.CardinalityValue;
 import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
@@ -612,4 +613,22 @@ public class GenericLiteralVisitor extends DefaultGremlinBaseVisitor<Object> {
         return result;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object visitTraversalCardinalityValue(final GremlinParser.TraversalCardinalityValueContext ctx) {
+        // could be CardinalityValue.single or single so grab the right child index based on number of children
+        final int idx = ctx.getChildCount() == 5 ? 1 : 0;
+        final String specifiedCard = ctx.children.get(idx).getText();
+        if (specifiedCard.equals(VertexProperty.Cardinality.single.name()))
+            return CardinalityValue.single(visitGenericLiteral(ctx.genericLiteral()));
+        else if (specifiedCard.equals(VertexProperty.Cardinality.list.name()))
+            return CardinalityValue.list(visitGenericLiteral(ctx.genericLiteral()));
+        else if (specifiedCard.equals(VertexProperty.Cardinality.set.name()))
+            return CardinalityValue.set(visitGenericLiteral(ctx.genericLiteral()));
+        else
+            throw new GremlinParserException(String.format(
+                    "A CardinalityValue must be defined as one of the available Cardinality values, not %s", specifiedCard));
+    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValue.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValue.java
new file mode 100644
index 0000000000..a2461060ca
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValue.java
@@ -0,0 +1,81 @@
+/*
+ * 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.structure.VertexProperty;
+
+import java.util.Objects;
+
+/**
+ * Wraps a {@link VertexProperty.Cardinality} with the value it is associated.
+ */
+public class CardinalityValue<T> {
+
+    private final VertexProperty.Cardinality cardinality;
+
+    private final T value;
+
+    private CardinalityValue(final VertexProperty.Cardinality cardinality, final T value) {
+        this.cardinality = cardinality;
+        this.value = value;
+    }
+
+    public static <T> CardinalityValue<T> single(final T value) {
+        return new CardinalityValue<T>(VertexProperty.Cardinality.single, value);
+    }
+
+    public static <T> CardinalityValue<T> list(final T value) {
+        return new CardinalityValue<T>(VertexProperty.Cardinality.list, value);
+    }
+
+    public static <T> CardinalityValue<T> set(final T value) {
+        return new CardinalityValue<T>(VertexProperty.Cardinality.set, value);
+    }
+
+    public VertexProperty.Cardinality getCardinality() {
+        return cardinality;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (!(o instanceof CardinalityValue)) return false;
+
+        CardinalityValue<?> that = (CardinalityValue<?>) o;
+
+        if (cardinality != that.cardinality) return false;
+        return Objects.equals(value, that.value);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = cardinality.hashCode();
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "cv[" + cardinality + ", " + Objects.toString(value) + "]";
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
index 1350b03202..7e066adc88 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
@@ -20,6 +20,8 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.tinkerpop.gremlin.process.traversal.CardinalityValue;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
@@ -673,4 +675,36 @@ public class GeneralLiteralVisitorTest {
             assertEquals(Double.NEGATIVE_INFINITY, GenericLiteralVisitor.instance().visitInfLiteral(ctx));
         }
     }
+
+    @RunWith(Parameterized.class)
+    public static class CardinalityValueTest {
+        @Parameterized.Parameter(value = 0)
+        public String script;
+
+        @Parameterized.Parameter(value = 1)
+        public CardinalityValue expected;
+
+        @Parameterized.Parameters()
+        public static Iterable<Object[]> generateTestParameters() {
+            return Arrays.asList(new Object[][]{
+                    {"single(\"test\")", CardinalityValue.single("test")},
+                    {"list(\"test\")", CardinalityValue.list("test")},
+                    {"set(\"test\")", CardinalityValue.set("test")},
+                    {"CardinalityValue.single(\"test\")", CardinalityValue.single("test")},
+                    {"CardinalityValue.list(\"test\")", CardinalityValue.list("test")},
+                    {"CardinalityValue.set(\"test\")", CardinalityValue.set("test")},
+                    {"single(1l)", CardinalityValue.single(1L)},
+                    {"list(1l)", CardinalityValue.list(1L)},
+                    {"set(1l)", CardinalityValue.set(1L)},
+            });
+        }
+
+        @Test
+        public void shouldParse() {
+            final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script));
+            final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer));
+            final GremlinParser.TraversalCardinalityValueContext ctx = parser.traversalCardinalityValue();
+            assertEquals(expected, GenericLiteralVisitor.instance().visitTraversalCardinalityValue(ctx));
+        }
+    }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValueTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValueTest.java
new file mode 100644
index 0000000000..221b13f085
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValueTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.structure.VertexProperty;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class CardinalityValueTest {
+
+    @Test
+    public void shouldCreateSingle() {
+        final CardinalityValue<String> cv = CardinalityValue.single("test");
+        assertEquals("test", cv.getValue());
+        assertEquals(VertexProperty.Cardinality.single, cv.getCardinality());
+    }
+
+    @Test
+    public void shouldCreateSet() {
+        final CardinalityValue<String> cv = CardinalityValue.set("test");
+        assertEquals("test", cv.getValue());
+        assertEquals(VertexProperty.Cardinality.set, cv.getCardinality());
+    }
+
+    @Test
+    public void shouldCreateList() {
+        final CardinalityValue<String> cv = CardinalityValue.list("test");
+        assertEquals("test", cv.getValue());
+        assertEquals(VertexProperty.Cardinality.list, cv.getCardinality());
+    }
+
+    @Test
+    public void shouldBeEqual() {
+        assertEquals(CardinalityValue.single("test"), CardinalityValue.single("test"));
+        assertEquals(CardinalityValue.single(1), CardinalityValue.single(1));
+        assertEquals(CardinalityValue.single(null), CardinalityValue.single(null));
+    }
+
+    @Test
+    public void shouldNotBeEqual() {
+        assertNotEquals(CardinalityValue.single(100), CardinalityValue.single("testing"));
+        assertNotEquals(CardinalityValue.single("test"), CardinalityValue.single("testing"));
+        assertNotEquals(CardinalityValue.single(100), CardinalityValue.single(1));
+        assertNotEquals(CardinalityValue.single("null"), CardinalityValue.single(null));
+    }
+}
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index 2e0ce504f5..3f9d7afaf3 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -915,6 +915,12 @@ traversalCardinality
     | 'list' | 'Cardinality.list'
     ;
 
+traversalCardinalityValue
+    : 'CardinalityValue.'? 'single' LPAREN genericLiteral RPAREN
+    | 'CardinalityValue.'? 'set' LPAREN genericLiteral RPAREN
+    | 'CardinalityValue.'? 'list' LPAREN genericLiteral RPAREN
+    ;
+
 traversalColumn
     : 'keys' | 'Column.keys'
     | 'values' | 'Column.values'
@@ -1376,6 +1382,7 @@ genericLiteral
 	| infLiteral
 	// Allow the generic literal to match specific gremlin tokens also
 	| traversalToken
+	| traversalCardinalityValue
 	| traversalCardinality
 	| traversalDirection
 	| traversalMerge


[tinkerpop] 02/02: TINKERPOP-2957 allow cardinality to be specified for mergeV()

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit df1297b12aecf0fbfcea67589d69167a0021eb8a
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Jun 21 11:49:52 2023 -0400

    TINKERPOP-2957 allow cardinality to be specified for mergeV()
    
    Provides a nicer way to allow users to control cardinality of property values when using mergeV(). A bit tough to get this in without changes to the serializers but by treating the CardinalityValue as a special form of Bytecode it allowed this change to keep the serializers sacred.
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/upgrade/release-3.6.x.asciidoc            |  36 ++++++
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   |  34 ++++--
 .../grammar/DefaultGremlinBaseVisitor.java         |   2 +-
 .../language/grammar/GenericLiteralVisitor.java    |  38 +++---
 .../language/grammar/TraversalMethodVisitor.java   |  10 ++
 .../gremlin/process/traversal/Bytecode.java        |   4 +-
 .../gremlin/process/traversal/Translator.java      |  14 ++-
 .../traversal/dsl/graph/GraphTraversal.java        |  24 ++++
 .../CardinalityValueTraversal.java}                |  47 ++++----
 .../traversal/step/map/MergeVertexStep.java        |  17 ++-
 .../process/traversal/step/util/Parameters.java    |   1 -
 .../traversal/translator/DotNetTranslator.java     |  10 ++
 .../traversal/translator/GolangTranslator.java     |  10 ++
 .../traversal/translator/GroovyTranslator.java     |   9 ++
 .../traversal/translator/JavascriptTranslator.java |   9 ++
 .../traversal/translator/PythonTranslator.java     |   9 ++
 .../gremlin/structure/VertexProperty.java          |  18 ++-
 .../structure/io/binary/GraphBinaryWriter.java     |   4 +-
 .../io/binary/types/TransformSerializer.java       |   2 +-
 .../grammar/GeneralLiteralVisitorTest.java         |  34 +++---
 .../gremlin/process/traversal/CardinalityTest.java |  68 +++++++++++
 .../process/traversal/CardinalityValueTest.java    |  64 ----------
 .../traversal/translator/DotNetTranslatorTest.java |   5 +
 .../traversal/translator/GolangTranslatorTest.java |   6 +
 .../traversal/translator/GroovyTranslatorTest.java |   6 +
 .../translator/JavascriptTranslatorTest.java       |   6 +
 .../traversal/translator/PythonTranslatorTest.java |  14 +++
 .../Process/Traversal/CardinalityValue.cs          |  88 ++++++++++++++
 .../Process/Traversal/GraphTraversal.cs            |   9 ++
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   6 +
 .../Process/Traversal/CardinalityValueTests.cs     |  55 +++++++++
 gremlin-go/driver/cucumber/gremlin.go              |   6 +
 gremlin-go/driver/traversal.go                     |  34 +++++-
 gremlin-javascript/build/generate.groovy           |   1 +
 .../lib/process/graph-traversal.js                 |  42 ++++++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   7 ++
 gremlin-language/src/main/antlr4/Gremlin.g4        |  16 +--
 gremlin-python/build/generate.groovy               |   2 +-
 .../python/gremlin_python/process/traversal.py     |  18 +++
 gremlin-python/src/main/python/radish/gremlin.py   |   8 +-
 .../gremlin/test/features/map/MergeVertex.feature  | 129 +++++++++++++++++++++
 42 files changed, 770 insertions(+), 153 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index de83cce8cf..4dc866639e 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 This release also includes changes from <<release-3-5-7, 3.5.7>>.
 
+* Allowed `mergeV()` to more easily define `Cardinality` values for properties for `onMatch` and `onCreate` options.
 * Added `text/plain` MIME type to the HTTP endpoint to return a Gremlin Console-like representation of the data.
 * Added GraphBinary serialization option to the HTTP endpoint.
 * Fixed bug with `fail` step not working with a `VertexProgram` running on the server.
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index d54ad7a38c..2154a3be6d 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -48,6 +48,42 @@ $ curl -H "Accept:text/plain" -X POST -d "{\"gremlin\":\"g.V()\"}" "http://local
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2947[TINKERPOP-2947]
 
+==== mergeV() and Cardinality
+
+The `mergeV()` step makes it much easier to write upsert-like traversals. Of course, if you had a graph that required
+the use of multi-properties, some of the ease of `mergeV()` was lost. It typically meant falling back to traversals
+using `sideEffect()` or similar direct uses of `property()` to allow it to work properly:
+
+[source,groovy]
+----
+g.mergeV([(T.id): '1234']).
+  option(onMatch, sideEffect(property(single,'age', 20).
+                             property(set,'city','miami')).constant([:]))
+----
+
+For this version, `mergeV()` gets two new bits of syntax. First, it is possible to individually define the cardinality
+for each property value in the `Map` for `onCreate` or `onMerge` events. Therefore, the above example could be written
+as:
+
+[source,groovy]
+----
+g.mergeV([(T.id): '1234']).
+  option(onMatch, ['age': single(20), 'city': set('miami')])
+----
+
+The other option available is to provide a default `Cardinality` to the `option()` as follows:
+
+[source,groovy]
+----
+g.mergeV([(T.id): '1234']).
+  option(onMatch, ['age': 20, 'city': set('miami')], single)
+----
+
+In the above example, any property value that does not have its cardinality explicitly defined, will be assumed to be
+the cardinality of the argument specified.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2957[TINKERPOP-2957]
+
 == TinkerPop 3.6.4
 
 *Release Date: May 12, 2023*
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index 91bc19cc0f..830ca2093e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -26,6 +26,7 @@ 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.lambda.CardinalityValueTraversal;
 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;
@@ -111,14 +112,33 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal
         if (object instanceof Bytecode.Binding)
             return translateObject(((Bytecode.Binding) object).value());
         else if (object instanceof Bytecode) {
-            try {
-                final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null);
-                for (final Bytecode.Instruction instruction : ((Bytecode) object).getStepInstructions()) {
-                    invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments());
+            // source based bytecode at this stage of translation could have special meaning, but generally this is
+            // going to spawn a new anonymous traversal.
+            final Bytecode bc = (Bytecode) object;
+            if (!bc.getSourceInstructions().isEmpty()) {
+                // currently, valid source instructions will be singly defined. would be odd to get this error. could
+                // be just bad construction from a language variant if it appears. maybe better as an assertion but
+                // third-party variants might benefit from this error
+                if (bc.getSourceInstructions().size() != 1) {
+                    throw new IllegalStateException("More than one source instruction defined in bytecode");
+                }
+
+                final Bytecode.Instruction inst = bc.getSourceInstructions().get(0);
+                if (inst.getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) {
+                    return CardinalityValueTraversal.from(inst);
+                } else {
+                    throw new IllegalStateException(String.format("Unknown source instruction for %s", inst.getOperator()));
+                }
+            } else {
+                try {
+                    final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null);
+                    for (final Bytecode.Instruction instruction : bc.getStepInstructions()) {
+                        invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments());
+                    }
+                    return traversal;
+                } catch (final Throwable e) {
+                    throw new IllegalStateException(e.getMessage());
                 }
-                return traversal;
-            } catch (final Throwable e) {
-                throw new IllegalStateException(e.getMessage());
             }
         } else if (object instanceof TraversalStrategyProxy) {
             final Map<String, Object> map = new HashMap<>();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index e24640eeb1..bbb79de7cd 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
@@ -1436,5 +1436,5 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im
 	 * {@inheritDoc}
 	 */
 	@Override
-	public T visitTraversalCardinalityValue(final GremlinParser.TraversalCardinalityValueContext ctx) { notImplemented(ctx); return null; }
+	public T visitTraversalMethod_option_Merge_Map_Cardinality(final GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) { notImplemented(ctx); return null; }
 }
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 7e604efe80..61863b2ae9 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
@@ -21,7 +21,6 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.TerminalNode;
 import org.apache.commons.text.StringEscapeUtils;
-import org.apache.tinkerpop.gremlin.process.traversal.CardinalityValue;
 import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Pick;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
@@ -509,7 +508,23 @@ public class GenericLiteralVisitor extends DefaultGremlinBaseVisitor<Object> {
      */
     @Override
     public Object visitTraversalCardinality(final GremlinParser.TraversalCardinalityContext ctx) {
-        return TraversalEnumParser.parseTraversalEnumFromContext(VertexProperty.Cardinality.class, ctx);
+        // could be Cardinality.single() the method or single the enum so grab the right child index based on
+        // number of children
+        if (ctx.getChildCount() == 1) {
+            return TraversalEnumParser.parseTraversalEnumFromContext(VertexProperty.Cardinality.class, ctx);
+        } else {
+            final int idx = ctx.getChildCount() == 5 ? 1 : 0;
+            final String specifiedCard = ctx.children.get(idx).getText();
+            if (specifiedCard.endsWith(VertexProperty.Cardinality.single.name()))
+                return VertexProperty.Cardinality.single(visitGenericLiteral(ctx.genericLiteral()));
+            else if (specifiedCard.endsWith(VertexProperty.Cardinality.list.name()))
+                return VertexProperty.Cardinality.list(visitGenericLiteral(ctx.genericLiteral()));
+            else if (specifiedCard.endsWith(VertexProperty.Cardinality.set.name()))
+                return VertexProperty.Cardinality.set(visitGenericLiteral(ctx.genericLiteral()));
+            else
+                throw new GremlinParserException(String.format(
+                        "A Cardinality value not recognized: %s", specifiedCard));
+        }
     }
 
     /**
@@ -612,23 +627,4 @@ public class GenericLiteralVisitor extends DefaultGremlinBaseVisitor<Object> {
         }
         return result;
     }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Object visitTraversalCardinalityValue(final GremlinParser.TraversalCardinalityValueContext ctx) {
-        // could be CardinalityValue.single or single so grab the right child index based on number of children
-        final int idx = ctx.getChildCount() == 5 ? 1 : 0;
-        final String specifiedCard = ctx.children.get(idx).getText();
-        if (specifiedCard.equals(VertexProperty.Cardinality.single.name()))
-            return CardinalityValue.single(visitGenericLiteral(ctx.genericLiteral()));
-        else if (specifiedCard.equals(VertexProperty.Cardinality.list.name()))
-            return CardinalityValue.list(visitGenericLiteral(ctx.genericLiteral()));
-        else if (specifiedCard.equals(VertexProperty.Cardinality.set.name()))
-            return CardinalityValue.set(visitGenericLiteral(ctx.genericLiteral()));
-        else
-            throw new GremlinParserException(String.format(
-                    "A CardinalityValue must be defined as one of the available Cardinality values, not %s", specifiedCard));
-    }
 }
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 111732cd07..adde508c96 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
@@ -1703,6 +1703,16 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
                 antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
 
+    @Override
+    public Traversal visitTraversalMethod_option_Merge_Map_Cardinality(final GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) {
+        if (ctx.nullLiteral() != null) {
+            return this.graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()), (Map) null);
+        }
+
+        return graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()),
+                (Map) new GenericLiteralVisitor(antlr).visitGenericLiteralMap(ctx.genericLiteralMap()));
+    }
+
     public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) {
         return ctx.nestedTraversalExpr().nestedTraversal()
                 .stream()
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
index dc8eca3add..5b88cea5fc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
@@ -47,7 +47,7 @@ import java.util.Set;
  *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class Bytecode implements Cloneable, Serializable {
+public class Bytecode implements Cloneable, Serializable {
 
     private static final Object[] EMPTY_ARRAY = new Object[]{};
 
@@ -56,7 +56,7 @@ public final class Bytecode implements Cloneable, Serializable {
 
     public Bytecode() {}
 
-    Bytecode(final String sourceName, final Object... arguments) {
+    public Bytecode(final String sourceName, final Object... arguments) {
         this.sourceInstructions.add(new Instruction(sourceName, flattenArguments(arguments)));
     }
 
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 0be7555797..120176821a 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,6 +19,7 @@
 
 package org.apache.tinkerpop.gremlin.process.traversal;
 
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -231,6 +232,11 @@ public interface Translator<S, T> {
              */
             protected abstract Script produceScript(final P<?> p);
 
+            /**
+             * Take the {@link Bytecode} and write the syntax for it directly to the member {@link #script} variable.
+             */
+            protected abstract Script produceCardinalityValue(final Bytecode o);
+
             /**
              *  For each operator argument, if withParameters set true, try parametrization as follows:
              *
@@ -272,7 +278,13 @@ public interface Translator<S, T> {
                 if (object instanceof Bytecode.Binding) {
                     return script.getBoundKeyOrAssign(withParameters, ((Bytecode.Binding) object).variable());
                 } else if (object instanceof Bytecode) {
-                    return produceScript(getAnonymousTraversalPrefix(), (Bytecode) object);
+                    final Bytecode bc = (Bytecode) object;
+                    if (bc.getSourceInstructions().size() == 1 &&
+                            bc.getSourceInstructions().get(0).getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) {
+                        return produceCardinalityValue(bc);
+                    } else {
+                        return produceScript(getAnonymousTraversalPrefix(), bc);
+                    }
                 } else if (object instanceof Traversal) {
                     return convertToScript(((Traversal) object).asAdmin().getBytecode());
                 } else if (object instanceof String) {
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 7fc1b3995a..c6bd6ea811 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
@@ -36,6 +36,7 @@ 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.CardinalityValueTraversal;
 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;
@@ -3271,6 +3272,29 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         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 Merge merge, final Map<Object, Object> m, final VertexProperty.Cardinality cardinality) {
+        this.asAdmin().getBytecode().addStep(Symbols.option, merge, m, cardinality);
+        // do explicit cardinality for every single pair in the map
+        for (Object k : m.keySet()) {
+            final Object o = m.get(k);
+            if (!(o instanceof CardinalityValueTraversal))
+                m.put(k, new CardinalityValueTraversal(cardinality, o));
+        }
+        ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption((M) merge, (Traversal.Admin<E, E2>) new ConstantTraversal<>(m).asAdmin());
+        return this;
+    }
+
     /**
      * This step modifies {@link #choose(Function)} to specifies the available choices that might be executed.
      *
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValue.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java
similarity index 58%
rename from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValue.java
rename to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java
index a2461060ca..8c13e0649b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValue.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java
@@ -16,36 +16,35 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.process.traversal;
+package org.apache.tinkerpop.gremlin.process.traversal.lambda;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 
 import java.util.Objects;
 
-/**
- * Wraps a {@link VertexProperty.Cardinality} with the value it is associated.
- */
-public class CardinalityValue<T> {
+public final class CardinalityValueTraversal extends AbstractLambdaTraversal {
 
     private final VertexProperty.Cardinality cardinality;
 
-    private final T value;
+    private final Object value;
+
+    private final Bytecode bytecode;
 
-    private CardinalityValue(final VertexProperty.Cardinality cardinality, final T value) {
+    public CardinalityValueTraversal(final VertexProperty.Cardinality cardinality, final Object value) {
         this.cardinality = cardinality;
         this.value = value;
+        this.bytecode = new Bytecode(CardinalityValueTraversal.class.getSimpleName(), cardinality.name(), value);
     }
 
-    public static <T> CardinalityValue<T> single(final T value) {
-        return new CardinalityValue<T>(VertexProperty.Cardinality.single, value);
-    }
-
-    public static <T> CardinalityValue<T> list(final T value) {
-        return new CardinalityValue<T>(VertexProperty.Cardinality.list, value);
+    public static CardinalityValueTraversal from(final Bytecode.Instruction inst) {
+        return new CardinalityValueTraversal(VertexProperty.Cardinality.valueOf(inst.getArguments()[0].toString()),
+                inst.getArguments()[1]);
     }
 
-    public static <T> CardinalityValue<T> set(final T value) {
-        return new CardinalityValue<T>(VertexProperty.Cardinality.set, value);
+    @Override
+    public Bytecode getBytecode() {
+        return this.bytecode;
     }
 
     public VertexProperty.Cardinality getCardinality() {
@@ -56,12 +55,18 @@ public class CardinalityValue<T> {
         return value;
     }
 
+    @Override
+    public String toString() {
+        return "[" + cardinality + ", " + value + "]";
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (this == o) return true;
-        if (!(o instanceof CardinalityValue)) return false;
+        if (!(o instanceof CardinalityValueTraversal)) return false;
+        if (!super.equals(o)) return false;
 
-        CardinalityValue<?> that = (CardinalityValue<?>) o;
+        final CardinalityValueTraversal that = (CardinalityValueTraversal) o;
 
         if (cardinality != that.cardinality) return false;
         return Objects.equals(value, that.value);
@@ -69,13 +74,9 @@ public class CardinalityValue<T> {
 
     @Override
     public int hashCode() {
-        int result = cardinality.hashCode();
+        int result = super.hashCode();
+        result = 31 * result + cardinality.hashCode();
         result = 31 * result + (value != null ? value.hashCode() : 0);
         return result;
     }
-
-    @Override
-    public String toString() {
-        return "cv[" + cardinality + ", " + Objects.toString(value) + "]";
-    }
 }
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
index 1408cfbfec..ed1e26a34a 100644
--- 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
@@ -29,12 +29,15 @@ import java.util.stream.Stream;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
 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.CloseableIterator;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
@@ -96,18 +99,28 @@ public class MergeVertexStep<S> extends MergeStep<S, Vertex, Map> {
                 validateMapInput(onMatchMap, true);
 
                 onMatchMap.forEach((key, value) -> {
+                    Object val = value;
+                    VertexProperty.Cardinality card = graph.features().vertex().getCardinality(key);
+
+                    // a value can be a traversal in the case where the user specifies the cardinality for the value.
+                    if (value instanceof CardinalityValueTraversal) {
+                        final CardinalityValueTraversal cardinalityValueTraversal =  (CardinalityValueTraversal) value;
+                        card = cardinalityValueTraversal.getCardinality();
+                        val = cardinalityValueTraversal.getValue();
+                    }
+
                     // 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);
+                        final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, val);
                         this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce));
                     }
 
                     // try to detect proper cardinality for the key according to the graph
-                    v.property(graph.features().vertex().getCardinality(key), key, value);
+                    v.property(card, key, val);
                 });
             });
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
index 55d69367df..d1654a344e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
@@ -135,7 +135,6 @@ public class Parameters implements Cloneable, Serializable {
     public <E> List<E> get(final Object key, final Supplier<E> defaultValue) {
         final List<E> list = (List<E>) this.parameters.get(key);
         return (null == list) ? (null == defaultValue ? Collections.emptyList() : Collections.singletonList(defaultValue.get())) : list;
-
     }
 
     /**
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 20ee8ec65c..5e37147b25 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
@@ -192,6 +192,16 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
             return o.toString();
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            final String card = inst.getArguments()[0].toString();
+            script.append("CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             final Iterator<?> iterator = o.iterator();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java
index ee1c38cbc0..dece576e72 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java
@@ -181,6 +181,16 @@ public final class GolangTranslator implements Translator.ScriptTranslator {
             return GO_PACKAGE_NAME + "Pick." + resolveSymbol(o.toString());
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            final String card = inst.getArguments()[0].toString();
+            script.append(GO_PACKAGE_NAME + "CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             final Iterator<?> iterator = o.iterator();
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 7fea7eb829..ad5498b003 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
@@ -206,6 +206,15 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
             return o.toString();
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            script.append("VertexProperty.Cardinality." + inst.getArguments()[0] + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             return produceScript(new ArrayList<>(o)).append(" as Set");
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 c03fd01ebb..1ebf25a34c 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
@@ -187,6 +187,15 @@ public final class JavascriptTranslator implements Translator.ScriptTranslator {
             return o.toString();
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            script.append("CardinalityValue." + inst.getArguments()[0] + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             return produceScript(new ArrayList<>(o));
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 41f8382643..e26b02f8ad 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
@@ -362,6 +362,15 @@ public final class PythonTranslator implements Translator.ScriptTranslator {
             return script;
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            script.append("CardinalityValue." + resolveSymbol(inst.getArguments()[0].toString()) + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         protected String resolveSymbol(final String methodName) {
             return SymbolHelper.toPython(methodName);
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java
index 4460f7d050..a1f45c145d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java
@@ -18,6 +18,10 @@
  */
 package org.apache.tinkerpop.gremlin.structure;
 
+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.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyVertexProperty;
 
 import java.util.Iterator;
@@ -40,7 +44,19 @@ public interface VertexProperty<V> extends Property<V>, Element {
     public static final String DEFAULT_LABEL = "vertexProperty";
 
     public enum Cardinality {
-        single, list, set
+        single, list, set;
+
+        public static CardinalityValueTraversal single(final Object value) {
+            return new CardinalityValueTraversal(single, value);
+        }
+
+        public static CardinalityValueTraversal list(final Object value) {
+            return new CardinalityValueTraversal(list, value);
+        }
+
+        public static CardinalityValueTraversal set(final Object value) {
+            return new CardinalityValueTraversal(set, value);
+        }
     }
 
     /**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java
index ab0b094329..80b1df0b1b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java
@@ -102,7 +102,7 @@ public class GraphBinaryWriter {
         if (serializer instanceof TransformSerializer) {
             // For historical reasons, there are types that need to be transformed into another type
             // before serialization, e.g., Map.Entry
-            TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer;
+            final TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer;
             write(transformSerializer.transform(value), buffer);
             return;
         }
@@ -118,7 +118,7 @@ public class GraphBinaryWriter {
      * <p>Note that for simple types, the provided information will be <code>null</code>.</p>
      */
     public <T> void writeFullyQualifiedNull(final Class<T> objectClass, Buffer buffer, final Object information) throws IOException {
-        TypeSerializer<T> serializer = registry.getSerializer(objectClass);
+        final TypeSerializer<T> serializer = registry.getSerializer(objectClass);
         serializer.write(null, buffer, this);
     }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java
index 97ccbbaea3..7275b1cce6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java
@@ -24,5 +24,5 @@ import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializer;
  * Represents a special TypeSerializer placeholder that transforms the value into another before serializing it.
  */
 public interface TransformSerializer<T> extends TypeSerializer<T> {
-    Object transform(T value);
+    Object transform(final T value);
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
index 7e066adc88..89b6fca15a 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
@@ -20,7 +20,7 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
-import org.apache.tinkerpop.gremlin.process.traversal.CardinalityValue;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.junit.Assert;
 import org.junit.Test;
@@ -677,25 +677,31 @@ public class GeneralLiteralVisitorTest {
     }
 
     @RunWith(Parameterized.class)
-    public static class CardinalityValueTest {
+    public static class CardinalityTest {
         @Parameterized.Parameter(value = 0)
         public String script;
 
         @Parameterized.Parameter(value = 1)
-        public CardinalityValue expected;
+        public Object expected;
 
         @Parameterized.Parameters()
         public static Iterable<Object[]> generateTestParameters() {
             return Arrays.asList(new Object[][]{
-                    {"single(\"test\")", CardinalityValue.single("test")},
-                    {"list(\"test\")", CardinalityValue.list("test")},
-                    {"set(\"test\")", CardinalityValue.set("test")},
-                    {"CardinalityValue.single(\"test\")", CardinalityValue.single("test")},
-                    {"CardinalityValue.list(\"test\")", CardinalityValue.list("test")},
-                    {"CardinalityValue.set(\"test\")", CardinalityValue.set("test")},
-                    {"single(1l)", CardinalityValue.single(1L)},
-                    {"list(1l)", CardinalityValue.list(1L)},
-                    {"set(1l)", CardinalityValue.set(1L)},
+                    {"single(\"test\")", VertexProperty.Cardinality.single("test")},
+                    {"list(\"test\")", VertexProperty.Cardinality.list("test")},
+                    {"set(\"test\")", VertexProperty.Cardinality.set("test")},
+                    {"Cardinality.single(\"test\")", VertexProperty.Cardinality.single("test")},
+                    {"Cardinality.list(\"test\")", VertexProperty.Cardinality.list("test")},
+                    {"Cardinality.set(\"test\")", VertexProperty.Cardinality.set("test")},
+                    {"single(1l)", VertexProperty.Cardinality.single(1L)},
+                    {"list(1l)", VertexProperty.Cardinality.list(1L)},
+                    {"set(1l)", VertexProperty.Cardinality.set(1L)},
+                    {"Cardinality.single", VertexProperty.Cardinality.single},
+                    {"Cardinality.list", VertexProperty.Cardinality.list},
+                    {"Cardinality.set", VertexProperty.Cardinality.set},
+                    {"single", VertexProperty.Cardinality.single},
+                    {"list", VertexProperty.Cardinality.list},
+                    {"set", VertexProperty.Cardinality.set},
             });
         }
 
@@ -703,8 +709,8 @@ public class GeneralLiteralVisitorTest {
         public void shouldParse() {
             final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script));
             final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer));
-            final GremlinParser.TraversalCardinalityValueContext ctx = parser.traversalCardinalityValue();
-            assertEquals(expected, GenericLiteralVisitor.instance().visitTraversalCardinalityValue(ctx));
+            final GremlinParser.TraversalCardinalityContext ctx = parser.traversalCardinality();
+            assertEquals(expected, GenericLiteralVisitor.instance().visitTraversalCardinality(ctx));
         }
     }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java
new file mode 100644
index 0000000000..b5c578e2f4
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class CardinalityTest {
+
+    @Test
+    public void shouldCreateSingle() {
+        final CardinalityValueTraversal t = VertexProperty.Cardinality.single("test");
+        assertEquals("test", t.getValue());
+        assertEquals(VertexProperty.Cardinality.single, t.getCardinality());
+    }
+
+    @Test
+    public void shouldCreateSet() {
+        final CardinalityValueTraversal t = VertexProperty.Cardinality.set("test");
+        assertEquals("test", t.getValue());
+        assertEquals(VertexProperty.Cardinality.set, t.getCardinality());
+    }
+
+    @Test
+    public void shouldCreateList() {
+        final CardinalityValueTraversal t = VertexProperty.Cardinality.list("test");
+        assertEquals("test", t.getValue());
+        assertEquals(VertexProperty.Cardinality.list, t.getCardinality());
+    }
+
+    @Test
+    public void shouldBeEqual() {
+        assertEquals(VertexProperty.Cardinality.single("test"), VertexProperty.Cardinality.single("test"));
+        assertEquals(VertexProperty.Cardinality.single(1), VertexProperty.Cardinality.single(1));
+        assertEquals(VertexProperty.Cardinality.single(null), VertexProperty.Cardinality.single(null));
+    }
+
+    @Test
+    public void shouldNotBeEqual() {
+        assertNotEquals(VertexProperty.Cardinality.single(100), VertexProperty.Cardinality.single("testing"));
+        assertNotEquals(VertexProperty.Cardinality.single("test"), VertexProperty.Cardinality.single("testing"));
+        assertNotEquals(VertexProperty.Cardinality.single(100), VertexProperty.Cardinality.single(1));
+        assertNotEquals(VertexProperty.Cardinality.single("null"), VertexProperty.Cardinality.single(null));
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValueTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValueTest.java
deleted file mode 100644
index 221b13f085..0000000000
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityValueTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.structure.VertexProperty;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-public class CardinalityValueTest {
-
-    @Test
-    public void shouldCreateSingle() {
-        final CardinalityValue<String> cv = CardinalityValue.single("test");
-        assertEquals("test", cv.getValue());
-        assertEquals(VertexProperty.Cardinality.single, cv.getCardinality());
-    }
-
-    @Test
-    public void shouldCreateSet() {
-        final CardinalityValue<String> cv = CardinalityValue.set("test");
-        assertEquals("test", cv.getValue());
-        assertEquals(VertexProperty.Cardinality.set, cv.getCardinality());
-    }
-
-    @Test
-    public void shouldCreateList() {
-        final CardinalityValue<String> cv = CardinalityValue.list("test");
-        assertEquals("test", cv.getValue());
-        assertEquals(VertexProperty.Cardinality.list, cv.getCardinality());
-    }
-
-    @Test
-    public void shouldBeEqual() {
-        assertEquals(CardinalityValue.single("test"), CardinalityValue.single("test"));
-        assertEquals(CardinalityValue.single(1), CardinalityValue.single(1));
-        assertEquals(CardinalityValue.single(null), CardinalityValue.single(null));
-    }
-
-    @Test
-    public void shouldNotBeEqual() {
-        assertNotEquals(CardinalityValue.single(100), CardinalityValue.single("testing"));
-        assertNotEquals(CardinalityValue.single("test"), CardinalityValue.single("testing"));
-        assertNotEquals(CardinalityValue.single(100), CardinalityValue.single(1));
-        assertNotEquals(CardinalityValue.single("null"), CardinalityValue.single(null));
-    }
-}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
index 28256f79fe..b3b7f1a855 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
@@ -199,6 +199,11 @@ public class DotNetTranslatorTest {
                 g.V().hasLabel("person").property(VertexProperty.Cardinality.single, "name", null)).getScript());
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertTranslation("CardinalityValue.Set(\"test\")", VertexProperty.Cardinality.set("test"));
+    }
+
     @Test
     public void shouldTranslateHasNull() {
         String script = translator.translate(g.V().has("k", (Object) null).asAdmin().getBytecode()).getScript();
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java
index 19b37b79eb..6ad0a95671 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java
@@ -63,6 +63,12 @@ public class GolangTranslatorTest {
         assertEquals("g.AddV(\"person\").Property(gremlingo.Cardinality.List, \"name\", \"marko\")", gremlinAsGo);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertEquals("g.Inject(gremlingo.CardinalityValue.Set(\"test\"))", translator.translate(
+                g.inject(VertexProperty.Cardinality.set("test")).asAdmin().getBytecode()).getScript());
+    }
+
     @Test
     public void shouldTranslateMultilineStrings() {
         final String gremlinAsGo = translator.translate(
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
index b9910fbbc8..499e76b4b4 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
@@ -34,6 +34,7 @@ import org.apache.tinkerpop.gremlin.structure.Column;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
@@ -160,6 +161,11 @@ public class GroovyTranslatorTest {
         assertTranslation("Column.keys", Column.keys);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertTranslation("VertexProperty.Cardinality.set(\"test\")", VertexProperty.Cardinality.set("test"));
+    }
+
     @Test
     public void shouldTranslateDateUsingLanguageTypeTranslator() {
         final Translator.ScriptTranslator t = GroovyTranslator.of("g",
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
index 98de2f988c..ba99d44969 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
@@ -32,6 +32,7 @@ import org.apache.tinkerpop.gremlin.structure.Column;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
@@ -120,6 +121,11 @@ public class JavascriptTranslatorTest {
         assertTranslation("Scope.local", Scope.local);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertTranslation("CardinalityValue.set(\"test\")", VertexProperty.Cardinality.set("test"));
+    }
+
     @Test
     public void shouldHaveValidToString() {
         assertEquals("translator[h:gremlin-javascript]", JavascriptTranslator.of("h").toString());
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java
index 9fee3ed4a9..ebbca186f8 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java
@@ -19,11 +19,13 @@
 package org.apache.tinkerpop.gremlin.process.traversal.translator;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
@@ -32,6 +34,9 @@ import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
 import org.apache.tinkerpop.gremlin.util.function.Lambda;
 import org.junit.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.hasLabel;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.inE;
@@ -62,6 +67,15 @@ public class PythonTranslatorTest {
         assertEquals("g.addV('person').property(Cardinality.list_,'name','marko')", gremlinAsPython);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        final Map<Object, Object> m = new HashMap<>();
+        m.put("name", VertexProperty.Cardinality.set("marko"));
+        final String gremlinAsPython = translator.translate(
+                g.mergeV(new HashMap<>()).option(Merge.onMatch, m).asAdmin().getBytecode()).getScript();
+        assertEquals("g.merge_v({}).option(Merge.on_match,{'name':CardinalityValue.set_('marko')})", gremlinAsPython);
+    }
+
     @Test
     public void shouldTranslateMultilineStrings() {
         final String gremlinAsPython = translator.translate(
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs
new file mode 100644
index 0000000000..efecabddae
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs
@@ -0,0 +1,88 @@
+#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.Collections.Generic;
+using System.Threading;
+
+namespace Gremlin.Net.Process.Traversal
+{
+    /// <summary>
+    ///     Holds a property value with the associated <see cref="Traversal.Cardinality" />.
+    /// </summary>
+    public class CardinalityValue : Bytecode
+    {
+        /// <summary>
+        ///     Gets the <see cref="Traversal.Cardinality" /> for the value.
+        /// </summary>
+        public Cardinality Cardinality
+        {
+            get
+            {
+                return this.SourceInstructions[0].Arguments[0];
+            }
+        }
+
+        /// <summary>
+        ///     Gets the value.
+        /// </summary>
+        public object Value
+        {
+            get
+            {
+                return this.SourceInstructions[0].Arguments[1];
+            }
+        }
+
+        /// <summary>
+        ///     Initializes a new instance of the <see cref="CardinalityValue" /> class.
+        /// </summary>
+        public CardinalityValue(Cardinality card, object val)
+        {
+            this.AddSource("CardinalityValueTraversal", card, val);
+        }
+
+        /// <summary>
+        ///     Creates a new <see cref="CardinalityValue" /> with a particular value and Single <see cref="Cardinality" />.
+        /// </summary>
+        public static CardinalityValue Single(object value)
+        {
+            return new CardinalityValue(Cardinality.Single, value);
+        }
+
+        /// <summary>
+        ///     Creates a new <see cref="CardinalityValue" /> with a particular value and Set <see cref="Cardinality" />.
+        /// </summary>
+        public static CardinalityValue Set(object value)
+        {
+            return new CardinalityValue(Cardinality.Set, value);
+        }
+
+        /// <summary>
+        ///     Creates a new <see cref="CardinalityValue" /> with a particular value and List <see cref="Cardinality" />.
+        /// </summary>
+        public static CardinalityValue List(object value)
+        {
+            return new CardinalityValue(Cardinality.List, value);
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index c6cf798f9a..82aa656ced 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1301,6 +1301,15 @@ namespace Gremlin.Net.Process.Traversal
             return Wrap<S, E>(this);
         }
 
+        /// <summary>
+        ///     Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, E> Option (object pickToken, IDictionary<object,object> traversalOption, Cardinality cardinality)
+        {
+            Bytecode.AddStep("option", pickToken, traversalOption, cardinality);
+            return Wrap<S, E>(this);
+        }
+
         /// <summary>
         ///     Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 6580b1908c..667630c571 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -707,6 +707,12 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_mergeV_hidden_id_key_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
                {"g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
                {"g_mergeV_hidden_label_value_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeVXname_markoX_optionXonMatch_age_listX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age" [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_setX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age", [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_setX31XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age", [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_singleX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"ag [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_33_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"age", 33}},Cardinality.Single), ( [...]
+               {"g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"name", "allen"},  [...]
                {"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>()}}, 
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs
new file mode 100644
index 0000000000..c30aaf3950
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs
@@ -0,0 +1,55 @@
+#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 Gremlin.Net.Process.Traversal;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Process.Traversal
+{
+    public class CardinalityValueTests
+    {
+        [Fact]
+        public void ShouldProduceSingleValue()
+        {
+            CardinalityValue val = CardinalityValue.Single("test");
+            Assert.Equal("test", val.Value);
+            Assert.Equal(Cardinality.Single, val.Cardinality);
+        }
+
+        [Fact]
+        public void ShouldProduceListValue()
+        {
+            CardinalityValue val = CardinalityValue.List("test");
+            Assert.Equal("test", val.Value);
+            Assert.Equal(Cardinality.List, val.Cardinality);
+        }
+
+        [Fact]
+        public void ShouldProduceSetValue()
+        {
+            CardinalityValue val = CardinalityValue.Set("test");
+            Assert.Equal("test", val.Value);
+            Assert.Equal(Cardinality.Set, val.Cardinality);
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 1444206a4b..84b752bf0f 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -678,6 +678,12 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_mergeV_hidden_id_key_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
     "g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
     "g_mergeV_hidden_label_value_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
+    "g_mergeVXname_markoX_optionXonMatch_age_listX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name":  [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_setX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": " [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_setX31XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": " [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_singleX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name" [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_33_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": [...]
+    "g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]int [...]
     "g_V_age_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Min()}}, 
     "g_V_foo_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Min()}}, 
     "g_V_name_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Min()}}, 
diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go
index c6a72a6f43..1529fbacb0 100644
--- a/gremlin-go/driver/traversal.go
+++ b/gremlin-go/driver/traversal.go
@@ -153,6 +153,36 @@ var Cardinality = cardinalities{
 	Set:    "set",
 }
 
+type cv struct {
+	Bytecode *Bytecode
+}
+
+type CardValue interface {
+	Single(val interface{}) Bytecode
+	Set(val interface{}) Bytecode
+	List(val interface{}) Bytecode
+}
+
+var CardinalityValue CardValue = &cv{}
+
+func (*cv) Single(val interface{}) Bytecode {
+	bc := Bytecode{}
+	bc.AddSource("CardinalityValueTraversal", Cardinality.Single, val)
+	return bc
+}
+
+func (*cv) Set(val interface{}) Bytecode {
+	bc := Bytecode{}
+	bc.AddSource("CardinalityValueTraversal", Cardinality.Set, val)
+	return bc
+}
+
+func (*cv) List(val interface{}) Bytecode {
+	bc := Bytecode{}
+	bc.AddSource("CardinalityValueTraversal", Cardinality.List, val)
+	return bc
+}
+
 type column string
 
 type columns struct {
@@ -286,8 +316,8 @@ type merges struct {
 var Merge = merges{
 	OnCreate: "onCreate",
 	OnMatch:  "onMatch",
-	OutV:  "outV",
-	InV:  "inV",
+	OutV:     "outV",
+	InV:      "inV",
 }
 
 type operator string
diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy
index c726d0e2ff..c7d92e0b29 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -95,6 +95,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
                     'const __ = graphTraversalModule.statics;\n' +
                     'const Barrier = traversalModule.barrier\n' +
                     'const Cardinality = traversalModule.cardinality\n' +
+                    'const CardinalityValue = graphTraversalModule.CardinalityValue;\n' +
                     'const Column = traversalModule.column\n' +
                     'const Direction = {\n' +
                     '    BOTH: traversalModule.direction.both,\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 292439ee6c..3884639d55 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
@@ -22,7 +22,7 @@
  */
 'use strict';
 
-const { Traversal } = require('./traversal');
+const { Traversal, cardinality } = require('./traversal');
 const { Transaction } = require('./transaction');
 const remote = require('../driver/remote-connection');
 const Bytecode = require('./bytecode');
@@ -1433,6 +1433,45 @@ class GraphTraversal extends Traversal {
   }
 }
 
+class CardinalityValue extends Bytecode {
+  /**
+   * Creates a new instance of {@link CardinalityValue}.
+   * @param {String} card
+   * @param {Object} value
+   */
+  constructor(card, value) {
+    super();
+    this.addSource('CardinalityValueTraversal', [card, value]);
+  }
+
+  /**
+   * Create a value with single cardinality.
+   * @param {Array} value
+   * @returns {CardinalityValue}
+   */
+  static single(value) {
+    return new CardinalityValue(cardinality.single, value);
+  }
+
+  /**
+   * Create a value with list cardinality.
+   * @param {Array} value
+   * @returns {CardinalityValue}
+   */
+  static list(value) {
+    return new CardinalityValue(cardinality.list, value);
+  }
+
+  /**
+   * Create a value with set cardinality.
+   * @param {Array} value
+   * @returns {CardinalityValue}
+   */
+  static set(value) {
+    return new CardinalityValue(cardinality.set, value);
+  }
+}
+
 function callOnEmptyTraversal(fnName, args) {
   const g = new GraphTraversal(null, null, new Bytecode());
   return g[fnName].apply(g, args);
@@ -1543,5 +1582,6 @@ const statics = {
 module.exports = {
   GraphTraversal,
   GraphTraversalSource,
+  CardinalityValue,
   statics,
 };
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 513042b2cb..5ef4ba4999 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
@@ -30,6 +30,7 @@ const { TraversalStrategies, VertexProgramStrategy, OptionsStrategy, ReadOnlyStr
 const __ = graphTraversalModule.statics;
 const Barrier = traversalModule.barrier
 const Cardinality = traversalModule.cardinality
+const CardinalityValue = graphTraversalModule.CardinalityValue;
 const Column = traversalModule.column
 const Direction = {
     BOTH: traversalModule.direction.both,
@@ -697,6 +698,12 @@ const gremlins = {
     g_mergeV_hidden_id_key_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
     g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
     g_mergeV_hidden_label_value_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
+    g_mergeVXname_markoX_optionXonMatch_age_listX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.list(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","mark [...]
+    g_mergeVXname_markoX_optionXonMatch_age_setX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.set(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","marko" [...]
+    g_mergeVXname_markoX_optionXonMatch_age_setX31XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.set(31)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",31) }, function({g}) { return g.V().has("person","name","marko" [...]
+    g_mergeVXname_markoX_optionXonMatch_age_singleX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.single(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name"," [...]
+    g_mergeVXname_markoX_optionXonMatch_age_33_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",33]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","marko") [...]
+    g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["name","allen"],["age",CardinalityValue.set(31)]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko") }, function({g}) {  [...]
     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() }], 
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index 3f9d7afaf3..5f37c5013f 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -569,6 +569,7 @@ traversalMethod_not
 traversalMethod_option
 	: 'option' LPAREN traversalPredicate COMMA nestedTraversal RPAREN #traversalMethod_option_Predicate_Traversal
 	| 'option' LPAREN traversalMerge COMMA (genericLiteralMap | nullLiteral) RPAREN #traversalMethod_option_Merge_Map
+	| 'option' LPAREN traversalMerge COMMA (genericLiteralMap | nullLiteral) COMMA traversalCardinality RPAREN #traversalMethod_option_Merge_Map_Cardinality
 	| '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
@@ -910,17 +911,17 @@ traversalDirection
     ;
 
 traversalCardinality
-    : 'single' | 'Cardinality.single'
+    : 'Cardinality.single' LPAREN genericLiteral RPAREN
+    | 'Cardinality.set' LPAREN genericLiteral RPAREN
+    | 'Cardinality.list' LPAREN genericLiteral RPAREN
+    | 'single' LPAREN genericLiteral RPAREN
+    | 'set' LPAREN genericLiteral RPAREN
+    | 'list' LPAREN genericLiteral RPAREN
+    | 'single' | 'Cardinality.single'
     | 'set' | 'Cardinality.set'
     | 'list' | 'Cardinality.list'
     ;
 
-traversalCardinalityValue
-    : 'CardinalityValue.'? 'single' LPAREN genericLiteral RPAREN
-    | 'CardinalityValue.'? 'set' LPAREN genericLiteral RPAREN
-    | 'CardinalityValue.'? 'list' LPAREN genericLiteral RPAREN
-    ;
-
 traversalColumn
     : 'keys' | 'Column.keys'
     | 'values' | 'Column.values'
@@ -1382,7 +1383,6 @@ genericLiteral
 	| infLiteral
 	// Allow the generic literal to match specific gremlin tokens also
 	| traversalToken
-	| traversalCardinalityValue
 	| traversalCardinality
 	| traversalDirection
 	| traversalMerge
diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy
index 6ccfa9ee86..78a065499b 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -93,7 +93,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, Merge, T, Pick, Operator, IO, WithOptions\n')
+                    'from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, 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/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index dc259d3d6f..a83048e3b0 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -817,6 +817,24 @@ class Bytecode(object):
             return Bytecode._create_graph_op("tx", "rollback")
 
 
+class CardinalityValue(Bytecode):
+    def __init__(self, cardinality, val):
+        super().__init__()
+        self.add_source("CardinalityValueTraversal", cardinality, val)
+
+    @classmethod
+    def single(cls, val):
+        return CardinalityValue(Cardinality.single, val)
+
+    @classmethod
+    def list_(cls, val):
+        return CardinalityValue(Cardinality.list_, val)
+
+    @classmethod
+    def set_(cls, val):
+        return CardinalityValue(Cardinality.set_, val)
+
+
 '''
 BINDINGS
 '''
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 998289bbc9..8075e8d134 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, Merge, T, Pick, Operator, IO, WithOptions
+from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, 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))], 
@@ -679,6 +679,12 @@ world.gremlins = {
     'g_mergeV_hidden_id_key_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
     'g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
     'g_mergeV_hidden_label_value_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
+    'g_mergeVXname_markoX_optionXonMatch_age_listX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.list_(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propert [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_setX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.set_(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propertie [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_setX31XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.set_(31)})), (lambda g:g.V().has('person','name','marko').has('age',31)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propertie [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_singleX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.single(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').prop [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_33_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':33},Cardinality.single)), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').properties( [...]
+    'g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'name':'allen','age':CardinalityValue.set_(31)},Cardinality.single)), (lambda g:g.V().has('person','name','marko')), (lambda g:g.V().has('person','name','allen').has('age',31)), (lambda g: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_())], 
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
index 91de838781..a71737e4fb 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
@@ -842,3 +842,132 @@ Feature: Step - mergeV()
       """
     When iterated to list
     Then the traversal will raise an error
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_listX33XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.list(33)])
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 4 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_setX33XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.set(33)])
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 4 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_setX31XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.set(31)])
+      """
+    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\", 31)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 3 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_singleX33XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.single(33)])
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_33_singleX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: 33], Cardinality.single)
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [name: "allen", age: Cardinality.set(31)], single)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 31)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\")"
+    And the graph should return 3 for count of "g.V().has(\"person\",\"name\",\"allen\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [name: "allen", age: Cardinality.single(31)], single)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+    And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 31)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").properties(\"age\")"
\ No newline at end of file