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

xiazcy pushed a commit to branch multi-label-experiment
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 6e521a75731848be3a37c78bd292b5127345a477
Author: Yang Xia <[email protected]>
AuthorDate: Wed Feb 25 18:25:32 2026 -0800

    multilabel prototype
---
 .../grammar/DefaultGremlinBaseVisitor.java         |  28 +++
 .../language/grammar/TraversalMethodVisitor.java   |  94 ++++++++
 .../grammar/TraversalSourceSpawnMethodVisitor.java |  28 ++-
 .../process/computer/util/ComputerGraph.java       |   5 +
 .../traversal/dsl/graph/GraphTraversal.java        | 112 ++++++++++
 .../traversal/dsl/graph/GraphTraversalSource.java  |  28 +++
 .../gremlin/process/traversal/dsl/graph/__.java    |  49 +++++
 .../process/traversal/step/map/ElementMapStep.java |  39 +++-
 .../process/traversal/step/map/LabelsStep.java     |  53 +++++
 .../traversal/step/map/MergeElementStep.java       |  59 ++++-
 .../traversal/step/map/MergeVertexStep.java        |  28 ++-
 .../traversal/step/map/PropertyMapStep.java        |  23 +-
 .../traversal/step/sideEffect/AddLabelStep.java    | 147 +++++++++++++
 .../traversal/step/sideEffect/DropLabelsStep.java  | 158 +++++++++++++
 .../process/traversal/step/util/HasContainer.java  |   9 +-
 .../process/traversal/step/util/WithOptions.java   |  12 +
 .../tinkerpop/gremlin/structure/Element.java       |  56 +++++
 .../gremlin/structure/VertexProperty.java          |  14 ++
 .../io/binary/types/VertexSerializer.java          |  25 ++-
 .../io/graphson/GraphSONSerializersV4.java         |  22 +-
 .../gremlin/structure/util/ElementHelper.java      |  37 ++++
 .../structure/util/detached/DetachedVertex.java    |  40 ++++
 .../structure/util/reference/ReferenceVertex.java  |  35 +++
 .../gremlin/structure/util/star/StarGraph.java     |   5 +
 gremlin-language/src/main/antlr4/Gremlin.g4        |  30 +++
 .../ser/binary/TypeSerializerFailureTests.java     |   2 +-
 .../tinkergraph/structure/AbstractTinkerGraph.java |  24 ++
 .../gremlin/tinkergraph/structure/TinkerEdge.java  |   5 +
 .../gremlin/tinkergraph/structure/TinkerGraph.java |   5 +-
 .../tinkergraph/structure/TinkerVertex.java        |  78 ++++++-
 .../process/traversal/step/map/LabelsStepTest.java | 106 +++++++++
 .../traversal/step/map/MergeVMultiLabelTest.java   | 154 +++++++++++++
 .../step/sideEffect/LabelMutationPropertyTest.java | 205 +++++++++++++++++
 .../step/sideEffect/LabelMutationStepTest.java     | 245 +++++++++++++++++++++
 .../TinkerVertexMultiLabelGremlinLangTest.java     | 121 ++++++++++
 .../structure/TinkerVertexMultiLabelTest.java      | 191 ++++++++++++++++
 36 files changed, 2236 insertions(+), 36 deletions(-)

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 a133cd0928..f50e47ae45 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
@@ -183,6 +183,10 @@ public class DefaultGremlinBaseVisitor<T> extends 
AbstractParseTreeVisitor<T> im
         * {@inheritDoc}
         */
        @Override public T visitTraversalMethod_addV_Traversal(final 
GremlinParser.TraversalMethod_addV_TraversalContext ctx) { notImplemented(ctx); 
return null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_addV_StringVarargs(final 
GremlinParser.TraversalMethod_addV_StringVarargsContext ctx) { 
notImplemented(ctx); return null; }
        /**
         * {@inheritDoc}
         */
@@ -539,6 +543,30 @@ public class DefaultGremlinBaseVisitor<T> extends 
AbstractParseTreeVisitor<T> im
         * {@inheritDoc}
         */
        @Override public T visitTraversalMethod_label(final 
GremlinParser.TraversalMethod_labelContext ctx) { notImplemented(ctx); return 
null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_labels(final 
GremlinParser.TraversalMethod_labelsContext ctx) { notImplemented(ctx); return 
null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_addLabel_String(final 
GremlinParser.TraversalMethod_addLabel_StringContext ctx) { 
notImplemented(ctx); return null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_addLabel_Traversal(final 
GremlinParser.TraversalMethod_addLabel_TraversalContext ctx) { 
notImplemented(ctx); return null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_dropLabels_Empty(final 
GremlinParser.TraversalMethod_dropLabels_EmptyContext ctx) { 
notImplemented(ctx); return null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_dropLabel_String(final 
GremlinParser.TraversalMethod_dropLabel_StringContext ctx) { 
notImplemented(ctx); return null; }
+       /**
+        * {@inheritDoc}
+        */
+       @Override public T visitTraversalMethod_dropLabel_Traversal(final 
GremlinParser.TraversalMethod_dropLabel_TraversalContext ctx) { 
notImplemented(ctx); return null; }
        /**
         * {@inheritDoc}
         */
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index f6967033e2..c46470092e 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
@@ -34,6 +34,7 @@ import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
 
@@ -97,6 +98,25 @@ public class TraversalMethodVisitor extends 
TraversalRootVisitor<GraphTraversal>
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_addV_StringVarargs(final 
GremlinParser.TraversalMethod_addV_StringVarargsContext ctx) {
+        final List<GremlinParser.StringArgumentContext> args = 
ctx.stringArgument();
+        final Object firstLiteralOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(0));
+        final String firstLabel = firstLiteralOrVar instanceof String ? 
(String) firstLiteralOrVar : ((GValue<String>) firstLiteralOrVar).get();
+        final Object secondLiteralOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(1));
+        final String secondLabel = secondLiteralOrVar instanceof String ? 
(String) secondLiteralOrVar : ((GValue<String>) secondLiteralOrVar).get();
+
+        final String[] moreLabels = new String[args.size() - 2];
+        for (int i = 2; i < args.size(); i++) {
+            final Object literalOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(i));
+            moreLabels[i - 2] = literalOrVar instanceof String ? (String) 
literalOrVar : ((GValue<String>) literalOrVar).get();
+        }
+        return this.graphTraversal.addV(firstLabel, secondLabel, moreLabels);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -978,6 +998,80 @@ public class TraversalMethodVisitor extends 
TraversalRootVisitor<GraphTraversal>
         return graphTraversal.label();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_labels(final 
GremlinParser.TraversalMethod_labelsContext ctx) {
+        return graphTraversal.labels();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_addLabel_String(final 
GremlinParser.TraversalMethod_addLabel_StringContext ctx) {
+        final List<GremlinParser.StringArgumentContext> args = 
ctx.stringArgument();
+        final Object firstLiteralOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(0));
+        final String firstLabel = firstLiteralOrVar instanceof String ? 
(String) firstLiteralOrVar : ((GValue<String>) firstLiteralOrVar).get();
+
+        if (args.size() == 1) {
+            return this.graphTraversal.addLabel(firstLabel);
+        } else {
+            final String[] moreLabels = new String[args.size() - 1];
+            for (int i = 1; i < args.size(); i++) {
+                final Object literalOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(i));
+                moreLabels[i - 1] = literalOrVar instanceof String ? (String) 
literalOrVar : ((GValue<String>) literalOrVar).get();
+            }
+            return this.graphTraversal.addLabel(firstLabel, moreLabels);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_addLabel_Traversal(final 
GremlinParser.TraversalMethod_addLabel_TraversalContext ctx) {
+        return 
this.graphTraversal.addLabel(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_dropLabels_Empty(final 
GremlinParser.TraversalMethod_dropLabels_EmptyContext ctx) {
+        return this.graphTraversal.dropLabels();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_dropLabel_String(final 
GremlinParser.TraversalMethod_dropLabel_StringContext ctx) {
+        final List<GremlinParser.StringArgumentContext> args = 
ctx.stringArgument();
+        final Object firstLiteralOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(0));
+        final String firstLabel = firstLiteralOrVar instanceof String ? 
(String) firstLiteralOrVar : ((GValue<String>) firstLiteralOrVar).get();
+
+        if (args.size() == 1) {
+            return this.graphTraversal.dropLabel(firstLabel);
+        } else {
+            final String[] moreLabels = new String[args.size() - 1];
+            for (int i = 1; i < args.size(); i++) {
+                final Object literalOrVar = 
antlr.argumentVisitor.visitStringArgument(args.get(i));
+                moreLabels[i - 1] = literalOrVar instanceof String ? (String) 
literalOrVar : ((GValue<String>) literalOrVar).get();
+            }
+            return this.graphTraversal.dropLabel(firstLabel, moreLabels);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GraphTraversal visitTraversalMethod_dropLabel_Traversal(final 
GremlinParser.TraversalMethod_dropLabel_TraversalContext ctx) {
+        return 
this.graphTraversal.dropLabel(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
index 7fccb00070..25b75641e5 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
@@ -22,6 +22,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -75,12 +76,29 @@ public class TraversalSourceSpawnMethodVisitor extends 
DefaultGremlinBaseVisitor
      */
     @Override
     public GraphTraversal visitTraversalSourceSpawnMethod_addV(final 
GremlinParser.TraversalSourceSpawnMethod_addVContext ctx) {
-        if (ctx.stringArgument() != null) {
-            final Object literalOrVar = 
antlr.argumentVisitor.visitStringArgument(ctx.stringArgument());
-            if (GValue.valueInstanceOf(literalOrVar, String.class)) {
-                return this.traversalSource.addV((GValue<String>) 
literalOrVar);
+        final List<GremlinParser.StringArgumentContext> stringArgs = 
ctx.stringArgument();
+        if (stringArgs != null && !stringArgs.isEmpty()) {
+            if (stringArgs.size() == 1) {
+                final Object literalOrVar = 
antlr.argumentVisitor.visitStringArgument(stringArgs.get(0));
+                if (GValue.valueInstanceOf(literalOrVar, String.class)) {
+                    return this.traversalSource.addV((GValue<String>) 
literalOrVar);
+                } else {
+                    return this.traversalSource.addV((String) literalOrVar);
+                }
             } else {
-                return this.traversalSource.addV((String) literalOrVar);
+                // Multi-label: addV("a", "b", ...)
+                final Object firstLiteralOrVar = 
antlr.argumentVisitor.visitStringArgument(stringArgs.get(0));
+                final String firstLabel = firstLiteralOrVar instanceof String 
? (String) firstLiteralOrVar : ((GValue<String>) firstLiteralOrVar).get();
+                // Create vertex with first label, then add remaining labels
+                GraphTraversal t = this.traversalSource.addV(firstLabel);
+                final Object secondLiteralOrVar = 
antlr.argumentVisitor.visitStringArgument(stringArgs.get(1));
+                final String secondLabel = secondLiteralOrVar instanceof 
String ? (String) secondLiteralOrVar : ((GValue<String>) 
secondLiteralOrVar).get();
+                final String[] moreLabels = new String[stringArgs.size() - 2];
+                for (int i = 2; i < stringArgs.size(); i++) {
+                    final Object literalOrVar = 
antlr.argumentVisitor.visitStringArgument(stringArgs.get(i));
+                    moreLabels[i - 2] = literalOrVar instanceof String ? 
(String) literalOrVar : ((GValue<String>) literalOrVar).get();
+                }
+                return t.addLabel(secondLabel, moreLabels);
             }
         } else if (ctx.nestedTraversal() != null) {
             return 
this.traversalSource.addV(anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/util/ComputerGraph.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/util/ComputerGraph.java
index 26b3e595e8..638d76dbce 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/util/ComputerGraph.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/util/ComputerGraph.java
@@ -456,6 +456,11 @@ public final class ComputerGraph implements Graph {
             throw GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead();
         }
 
+        @Override
+        public Set<String> labels() {
+            throw GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead();
+        }
+
         @Override
         public Graph graph() {
             return ComputerGraph.this;
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 286cd68b55..c0e6c221b3 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
@@ -65,6 +65,8 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStepPl
 import org.apache.tinkerpop.gremlin.process.traversal.step.PropertiesHolder;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStepPlaceholder;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStepPlaceholder;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddLabelStep;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.DropLabelsStep;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStepContract;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStepContract;
 import org.apache.tinkerpop.gremlin.process.traversal.step.FromToModulating;
@@ -132,6 +134,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.IntersectStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.LTrimGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.LTrimLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.LabelStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.LabelsStep;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.LambdaCollectingBarrierStep;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.LambdaFlatMapStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.LambdaMapStep;
@@ -350,12 +353,26 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
      * @return the traversal with an appended {@link LabelStep}.
      * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#label-step";
 target="_blank">Reference Documentation - Label Step</a>
      * @since 3.0.0-incubating
+     * @deprecated As of release 4.0.0, replaced by {@link #labels()}.
      */
+    @Deprecated
     public default GraphTraversal<S, String> label() {
         this.asAdmin().getGremlinLang().addStep(Symbols.label);
         return this.asAdmin().addStep(new LabelStep<>(this.asAdmin()));
     }
 
+    /**
+     * Map the {@link Element} to its labels, emitting each label as a 
separate traverser.
+     * For vertices with multiple labels, each label is emitted individually.
+     *
+     * @return the traversal with an appended {@link LabelsStep}.
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, String> labels() {
+        this.asAdmin().getGremlinLang().addStep(Symbols.labels);
+        return this.asAdmin().addStep(new LabelsStep<>(this.asAdmin()));
+    }
+
     /**
      * Map the <code>E</code> object to itself. In other words, a "no op."
      *
@@ -1430,6 +1447,31 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         return this.asAdmin().addStep(new 
AddVertexStepPlaceholder<>(this.asAdmin(), (String) null));
     }
 
+    /**
+     * Adds a {@link Vertex} with multiple labels. Use this method to create 
multi-labeled vertices.
+     * Creates the vertex with the first label, then adds the remaining labels.
+     *
+     * @param label1     the first label
+     * @param label2     the second label
+     * @param moreLabels additional labels
+     * @return the traversal with the {@link AddVertexStepContract} added
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, Vertex> addV(final String label1, final 
String label2, final String... moreLabels) {
+        if (null == label1) throw new IllegalArgumentException("vertexLabel 
cannot be null");
+        if (null == label2) throw new IllegalArgumentException("vertexLabel 
cannot be null");
+        for (final String l : moreLabels) {
+            if (null == l) throw new IllegalArgumentException("vertexLabel 
cannot be null");
+        }
+        this.asAdmin().getGremlinLang().addStep(Symbols.addV, label1, label2, 
moreLabels);
+        this.asAdmin().addStep(new AddVertexStepPlaceholder<>(this.asAdmin(), 
label1));
+        // Add the AddLabelStep directly to avoid double-recording in 
GremlinLang.
+        // The addV step above already recorded all labels; calling 
t.addLabel() would
+        // record an additional addLabel() step in GremlinLang, producing 
incorrect output
+        // like g.addV("a","b").addLabel("b") instead of g.addV("a","b").
+        return this.asAdmin().addStep(new AddLabelStep<>(this.asAdmin(), 
label2, moreLabels));
+    }
+
     /**
      * Performs a merge (i.e. upsert) style operation for an {@link Vertex} 
using the incoming {@code Map} traverser as
      * an argument. The {@code Map} represents search criteria and will match 
each of the supplied key/value pairs where
@@ -3427,6 +3469,72 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         return this.asAdmin().addStep(new DropStep<>(this.asAdmin()));
     }
 
+    /**
+     * Adds one or more labels to the current element. This is a side-effect 
step that passes the
+     * element through unchanged.
+     *
+     * @param label      the first label to add
+     * @param moreLabels additional labels to add
+     * @return the traversal with an appended {@link AddLabelStep}
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> addLabel(final String label, final 
String... moreLabels) {
+        this.asAdmin().getGremlinLang().addStep(Symbols.addLabel, label, 
moreLabels);
+        return this.asAdmin().addStep((AddLabelStep) new 
AddLabelStep<>(this.asAdmin(), label, moreLabels));
+    }
+
+    /**
+     * Adds dynamically computed labels to the current element. This is a 
side-effect step that passes the
+     * element through unchanged.
+     *
+     * @param labelTraversal the traversal that produces labels to add
+     * @return the traversal with an appended {@link AddLabelStep}
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> addLabel(final Traversal<?, String> 
labelTraversal) {
+        this.asAdmin().getGremlinLang().addStep(Symbols.addLabel, 
labelTraversal);
+        return this.asAdmin().addStep((AddLabelStep) new 
AddLabelStep(this.asAdmin(), labelTraversal.asAdmin()));
+    }
+
+    /**
+     * Removes all labels from the current element, triggering the provider's 
default label behavior.
+     * This is a side-effect step that passes the element through unchanged.
+     *
+     * @return the traversal with an appended {@link DropLabelsStep}
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> dropLabels() {
+        this.asAdmin().getGremlinLang().addStep(Symbols.dropLabels);
+        return this.asAdmin().addStep((DropLabelsStep) new 
DropLabelsStep<>(this.asAdmin()));
+    }
+
+    /**
+     * Removes specific labels from the current element. This is a side-effect 
step that passes the
+     * element through unchanged.
+     *
+     * @param label      the first label to remove
+     * @param moreLabels additional labels to remove
+     * @return the traversal with an appended {@link DropLabelsStep}
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> dropLabel(final String label, final 
String... moreLabels) {
+        this.asAdmin().getGremlinLang().addStep(Symbols.dropLabel, label, 
moreLabels);
+        return this.asAdmin().addStep((DropLabelsStep) new 
DropLabelsStep<>(this.asAdmin(), label, moreLabels));
+    }
+
+    /**
+     * Removes a dynamically computed label from the current element. This is 
a side-effect step that passes the
+     * element through unchanged.
+     *
+     * @param labelTraversal the traversal that produces the label to remove
+     * @return the traversal with an appended {@link DropLabelsStep}
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> dropLabel(final Traversal<?, String> 
labelTraversal) {
+        this.asAdmin().getGremlinLang().addStep(Symbols.dropLabel, 
labelTraversal);
+        return this.asAdmin().addStep((DropLabelsStep) new 
DropLabelsStep(this.asAdmin(), labelTraversal.asAdmin()));
+    }
+
     /**
      * Filters <code>E</code> lists given the provided {@code predicate}.
      *
@@ -4724,6 +4832,7 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         public static final String flatMap = "flatMap";
         public static final String id = "id";
         public static final String label = "label";
+        public static final String labels = "labels";
         public static final String identity = "identity";
         public static final String constant = "constant";
         public static final String V = "V";
@@ -4830,6 +4939,9 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         public static final String sample = "sample";
 
         public static final String drop = "drop";
+        public static final String addLabel = "addLabel";
+        public static final String dropLabels = "dropLabels";
+        public static final String dropLabel = "dropLabel";
 
         public static final String sideEffect = "sideEffect";
         public static final String cap = "cap";
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
index 9e1d350503..4c13a27384 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
@@ -31,6 +31,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionStep;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStartStepPlaceholder;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStepPlaceholder;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddLabelStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStep;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStepPlaceholder;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
@@ -363,6 +364,33 @@ public class GraphTraversalSource implements 
TraversalSource {
         return traversal.addStep(new AddVertexStartStepPlaceholder(traversal, 
vertexLabel));
     }
 
+    /**
+     * Spawns a {@link GraphTraversal} by adding a vertex with multiple labels.
+     * Creates the vertex with the first label, then adds the remaining labels.
+     *
+     * @param label1     the first label
+     * @param label2     the second label
+     * @param moreLabels additional labels
+     * @return the traversal with the vertex added
+     * @since 4.0.0
+     */
+    public GraphTraversal<Vertex, Vertex> addV(final String label1, final 
String label2, final String... moreLabels) {
+        if (null == label1) throw new IllegalArgumentException("vertexLabel 
cannot be null");
+        if (null == label2) throw new IllegalArgumentException("vertexLabel 
cannot be null");
+        for (final String l : moreLabels) {
+            if (null == l) throw new IllegalArgumentException("vertexLabel 
cannot be null");
+        }
+        final GraphTraversalSource clone = this.clone();
+        clone.gremlinLang.addStep(GraphTraversal.Symbols.addV, label1, label2, 
moreLabels);
+        final GraphTraversal.Admin<Vertex, Vertex> traversal = new 
DefaultGraphTraversal<>(clone);
+        traversal.addStep(new AddVertexStartStepPlaceholder(traversal, 
label1));
+        // Add the AddLabelStep directly to avoid double-recording in 
GremlinLang.
+        // The addV step above already recorded all labels; calling 
t.addLabel() would
+        // record an additional addLabel() step in GremlinLang, producing 
incorrect output
+        // like g.addV("a","b").addLabel("b") instead of g.addV("a","b").
+        return traversal.addStep(new AddLabelStep<>(traversal, label2, 
moreLabels));
+    }
+
     /**
      * Spawns a {@link GraphTraversal} by adding an edge with the specified 
label.
      *
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index 65007d9631..37ed57f580 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -126,6 +126,48 @@ public class __ {
         return __.<A>start().label();
     }
 
+    /**
+     * @see GraphTraversal#labels()
+     */
+    public static <A extends Element> GraphTraversal<A, String> labels() {
+        return __.<A>start().labels();
+    }
+
+    /**
+     * @see GraphTraversal#addLabel(String, String...)
+     */
+    public static <A extends Element> GraphTraversal<A, A> addLabel(final 
String label, final String... moreLabels) {
+        return __.<A>start().addLabel(label, moreLabels);
+    }
+
+    /**
+     * @see GraphTraversal#addLabel(Traversal)
+     */
+    public static <A extends Element> GraphTraversal<A, A> addLabel(final 
Traversal<?, String> labelTraversal) {
+        return __.<A>start().addLabel(labelTraversal);
+    }
+
+    /**
+     * @see GraphTraversal#dropLabels()
+     */
+    public static <A extends Element> GraphTraversal<A, A> dropLabels() {
+        return __.<A>start().dropLabels();
+    }
+
+    /**
+     * @see GraphTraversal#dropLabel(String, String...)
+     */
+    public static <A extends Element> GraphTraversal<A, A> dropLabel(final 
String label, final String... moreLabels) {
+        return __.<A>start().dropLabel(label, moreLabels);
+    }
+
+    /**
+     * @see GraphTraversal#dropLabel(Traversal)
+     */
+    public static <A extends Element> GraphTraversal<A, A> dropLabel(final 
Traversal<?, String> labelTraversal) {
+        return __.<A>start().dropLabel(labelTraversal);
+    }
+
     /**
      * @see GraphTraversal#id()
      */
@@ -654,6 +696,13 @@ public class __ {
         return __.<A>start().addV();
     }
 
+    /**
+     * @see GraphTraversal#addV(String, String, String...)
+     */
+    public static <A> GraphTraversal<A, Vertex> addV(final String label1, 
final String label2, final String... moreLabels) {
+        return __.<A>start().addV(label1, label2, moreLabels);
+    }
+
     /**
      * @see GraphTraversal#mergeV()
      */
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
index 71d919ca18..152301e0d7 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
@@ -20,8 +20,12 @@ package 
org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Configuring;
 import org.apache.tinkerpop.gremlin.process.traversal.step.GraphComputing;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
@@ -46,16 +50,32 @@ import java.util.Set;
  * @author Daniel Kuppitz (http://gremlin.guru)
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public class ElementMapStep<K,E> extends ScalarMapStep<Element, Map<K, E>> 
implements TraversalParent, GraphComputing {
+public class ElementMapStep<K,E> extends ScalarMapStep<Element, Map<K, E>> 
implements TraversalParent, GraphComputing, Configuring {
 
     protected final String[] propertyKeys;
     private boolean onGraphComputer = false;
+    private boolean multilabel = false;
+    private final Parameters parameters = new Parameters();
 
     public ElementMapStep(final Traversal.Admin traversal, final String... 
propertyKeys) {
         super(traversal);
         this.propertyKeys = propertyKeys;
     }
 
+    @Override
+    public void configure(final Object... keyValues) {
+        if (keyValues[0].equals(WithOptions.multilabel)) {
+            this.multilabel = true;
+        } else {
+            this.parameters.set(this, keyValues);
+        }
+    }
+
+    @Override
+    public Parameters getParameters() {
+        return this.parameters;
+    }
+
     @Override
     protected Map<K, E> map(final Traverser.Admin<Element> traverser) {
         final Map<Object, Object> map = new LinkedHashMap<>();
@@ -65,7 +85,11 @@ public class ElementMapStep<K,E> extends 
ScalarMapStep<Element, Map<K, E>> imple
             map.put(T.key, ((VertexProperty<?>) element).key());
             map.put(T.value, ((VertexProperty<?>) element).value());
         } else {
-            map.put(T.label, element.label());
+            if (isMultilabelEnabled()) {
+                map.put(T.label, element.labels());
+            } else {
+                map.put(T.label, element.label());
+            }
         }
 
         if (element instanceof Edge) {
@@ -102,6 +126,17 @@ public class ElementMapStep<K,E> extends 
ScalarMapStep<Element, Map<K, E>> imple
         return onGraphComputer;
     }
 
+    /**
+     * Checks if multilabel mode is enabled either via step-level {@code 
.with(WithOptions.multilabel)}
+     * or source-level {@code g.with("multilabel")}.
+     */
+    private boolean isMultilabelEnabled() {
+        if (this.multilabel) return true;
+        return 
getTraversal().getStrategies().getStrategy(OptionsStrategy.class)
+                .map(os -> os.getOptions().containsKey("multilabel") || 
os.getOptions().containsKey(WithOptions.multilabel))
+                .orElse(false);
+    }
+
     public String[] getPropertyKeys() {
         return propertyKeys;
     }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelsStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelsStep.java
new file mode 100644
index 0000000000..a5ba807176
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelsStep.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Maps an {@link Element} to its labels, emitting each label as a separate 
traverser.
+ * For vertices with multiple labels, each label is emitted individually.
+ * For edges, the single label is emitted.
+ *
+ * @since 4.0.0
+ */
+public class LabelsStep<S extends Element> extends FlatMapStep<S, String> {
+
+    public LabelsStep(final Traversal.Admin traversal) {
+        super(traversal);
+    }
+
+    @Override
+    protected Iterator<String> flatMap(final Traverser.Admin<S> traverser) {
+        return traverser.get().labels().iterator();
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return Collections.singleton(TraverserRequirement.OBJECT);
+    }
+}
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeElementStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeElementStep.java
index daae4816fd..68f4380227 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeElementStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeElementStep.java
@@ -252,7 +252,25 @@ public abstract class MergeElementStep<S, E, C> extends 
FlatMapStep<S, E>
             final Object v = e.getValue();
 
             if (ignoreTokens) {
-                if (!(k instanceof String)) {
+                // Allow T.label in onMatch for multi-label replacement support
+                if (k == T.label) {
+                    if (v instanceof String) {
+                        ElementHelper.validateLabel((String) v);
+                    } else if (v instanceof java.util.Collection) {
+                        for (final Object label : (java.util.Collection<?>) v) 
{
+                            if (!(label instanceof String)) {
+                                throw new 
IllegalArgumentException(String.format(
+                                        "option(onMatch) expects T.label 
collection to contain only Strings - found: %s",
+                                        label.getClass().getSimpleName()));
+                            }
+                            ElementHelper.validateLabel((String) label);
+                        }
+                    } else {
+                        throw new IllegalArgumentException(String.format(
+                                "option(onMatch) expects T.label value to be 
String or Collection<String> - found: %s",
+                                v.getClass().getSimpleName()));
+                    }
+                } else if (!(k instanceof String)) {
                     throw new 
IllegalArgumentException(String.format("option(onMatch) expects keys in Map to 
be of String - check: %s", k));
                 } else {
                     ElementHelper.validateProperty((String) k, v);
@@ -269,12 +287,21 @@ public abstract class MergeElementStep<S, E, C> extends 
FlatMapStep<S, E>
                             op, allowedTokens, k));
                 }
                 if (k == T.label) {
-                    if (!(v instanceof String)) {
-                        throw new IllegalArgumentException(String.format(
-                                "%s() and option(onCreate) args expect T.label 
value to be of String - found: %s", op,
-                                v.getClass().getSimpleName()));
-                    } else {
+                    if (v instanceof String) {
                         ElementHelper.validateLabel((String) v);
+                    } else if (v instanceof java.util.Collection) {
+                        for (final Object label : (java.util.Collection<?>) v) 
{
+                            if (!(label instanceof String)) {
+                                throw new 
IllegalArgumentException(String.format(
+                                        "%s() expects T.label collection to 
contain only Strings - found: %s",
+                                        op, label.getClass().getSimpleName()));
+                            }
+                            ElementHelper.validateLabel((String) label);
+                        }
+                    } else {
+                        throw new IllegalArgumentException(String.format(
+                                "%s() and option(onCreate) args expect T.label 
value to be String or Collection<String> - found: %s",
+                                op, v.getClass().getSimpleName()));
                     }
                 }
                 if (k == Direction.OUT && v instanceof Merge && v != 
Merge.outV) {
@@ -335,10 +362,10 @@ public abstract class MergeElementStep<S, E, C> extends 
FlatMapStep<S, E>
 
         final Graph graph = getGraph();
         final Object id = search.get(T.id);
-        final String label = (String) search.get(T.label);
+        final Object labelValue = search.get(T.label);
 
         GraphTraversal t = searchVerticesTraversal(graph, id);
-        t = searchVerticesLabelConstraint(t, label);
+        t = searchVerticesLabelConstraint(t, labelValue);
         t = searchVerticesPropertyConstraints(t, search);
 
         // this should auto-close the underlying traversal
@@ -349,8 +376,20 @@ public abstract class MergeElementStep<S, E, C> extends 
FlatMapStep<S, E>
         return id != null ? graph.traversal().V(id) : graph.traversal().V();
     }
 
-    protected GraphTraversal searchVerticesLabelConstraint(GraphTraversal t, 
final String label) {
-        return label != null ? t.hasLabel(label) : t;
+    protected GraphTraversal searchVerticesLabelConstraint(GraphTraversal t, 
final Object labelValue) {
+        if (labelValue == null) {
+            return t;
+        }
+        if (labelValue instanceof String) {
+            return t.hasLabel((String) labelValue);
+        } else if (labelValue instanceof java.util.Collection) {
+            // Multi-label: AND semantics - must have ALL specified labels
+            for (final Object label : (java.util.Collection<?>) labelValue) {
+                t = t.hasLabel((String) label);
+            }
+            return t;
+        }
+        return t;
     }
 
     protected GraphTraversal searchVerticesPropertyConstraints(GraphTraversal 
t, final Map search) {
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 279af6519d..a2c59516b4 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
@@ -99,12 +99,32 @@ public class MergeVertexStep<S> extends MergeElementStep<S, 
Vertex, Map<Object,
                 traverser.set((S) v);
 
                 // assume good input from GraphTraversal - folks might drop in 
a T here even though it is immutable
-                final Map<String, Object> onMatchMap = 
materializeMap(traverser, onMatchTraversal);
+                final Map onMatchMap = materializeMap(traverser, 
onMatchTraversal);
                 validateMapInput(onMatchMap, true);
 
                 onMatchMap.forEach((key, value) -> {
+                    // Handle T.label replacement for multi-label support
+                    if (T.label.equals(key) || 
T.label.getAccessor().equals(key)) {
+                        // Drop all existing labels and replace with new ones
+                        v.dropLabels();
+                        if (value instanceof String) {
+                            v.addLabel((String) value);
+                        } else if (value instanceof java.util.Collection) {
+                            final java.util.Collection<?> labels = 
(java.util.Collection<?>) value;
+                            if (!labels.isEmpty()) {
+                                final String[] labelArray = labels.stream()
+                                        .map(l -> (String) l)
+                                        .toArray(String[]::new);
+                                v.addLabel(labelArray[0],
+                                        
java.util.Arrays.copyOfRange(labelArray, 1, labelArray.length));
+                            }
+                            // Empty collection = use default label behavior 
(already handled by dropLabels())
+                        }
+                        return;
+                    }
+
                     Object val = value;
-                    VertexProperty.Cardinality card = 
graph.features().vertex().getCardinality(key);
+                    VertexProperty.Cardinality card = 
graph.features().vertex().getCardinality((String) key);
 
                     // a value can be a traversal in the case where the user 
specifies the cardinality for the value.
                     if (value instanceof CardinalityValueTraversal) {
@@ -115,10 +135,10 @@ public class MergeVertexStep<S> extends 
MergeElementStep<S, Vertex, Map<Object,
 
                     // trigger callbacks for eventing - in this case, it's a 
VertexPropertyChangedEvent. if there's no
                     // registry/callbacks then just set the property
-                    EventUtil.registerVertexPropertyChange(callbackRegistry, 
getTraversal(), v, key, val);
+                    EventUtil.registerVertexPropertyChange(callbackRegistry, 
getTraversal(), v, (String) key, val);
 
                     // try to detect proper cardinality for the key according 
to the graph
-                    v.property(card, key, val);
+                    v.property(card, (String) key, val);
                 });
             });
         }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
index 3534b7e313..9ce9f13331 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
@@ -25,6 +25,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.Configuring;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalProduct;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
@@ -58,6 +59,7 @@ public class PropertyMapStep<K,E> extends 
ScalarMapStep<Element, Map<K, E>>
 
     protected int tokens;
     protected Traversal.Admin<Element, ? extends Property> propertyTraversal;
+    protected boolean multilabel = false;
 
     protected Parameters parameters = new Parameters();
     protected Traversal.Admin<K, E> valueTraversal;
@@ -97,6 +99,8 @@ public class PropertyMapStep<K,E> extends 
ScalarMapStep<Element, Map<K, E>>
                     this.tokens |= (int) keyValues[i];
                 }
             }
+        } else if (keyValues[0].equals(WithOptions.multilabel)) {
+            this.multilabel = true;
         } else {
             this.parameters.set(this, keyValues);
         }
@@ -200,11 +204,28 @@ public class PropertyMapStep<K,E> extends 
ScalarMapStep<Element, Map<K, E>>
                 if (includeToken(WithOptions.keys)) map.put(T.key, 
getVertexPropertyKey((VertexProperty<?>) element));
                 if (includeToken(WithOptions.values)) map.put(T.value, 
getVertexPropertyValue((VertexProperty<?>) element));
             } else {
-                if (includeToken(WithOptions.labels)) map.put(T.label, 
getElementLabel(element));
+                if (includeToken(WithOptions.labels)) {
+                    if (isMultilabelEnabled()) {
+                        map.put(T.label, element.labels());
+                    } else {
+                        map.put(T.label, getElementLabel(element));
+                    }
+                }
             }
         }
     }
 
+    /**
+     * Checks if multilabel mode is enabled either via step-level {@code 
.with(WithOptions.multilabel)}
+     * or source-level {@code g.with("multilabel")}.
+     */
+    private boolean isMultilabelEnabled() {
+        if (this.multilabel) return true;
+        return 
getTraversal().getStrategies().getStrategy(OptionsStrategy.class)
+                .map(os -> os.getOptions().containsKey("multilabel") || 
os.getOptions().containsKey(WithOptions.multilabel))
+                .orElse(false);
+    }
+
     protected Object getElementId(Element element){
         return element.id();
     }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddLabelStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddLabelStep.java
new file mode 100644
index 0000000000..c239623601
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddLabelStep.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.event.EventUtil;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Side-effect step that adds labels to the current element by calling {@link 
Element#addLabel(String, String...)}.
+ * Providers that support label mutation must override {@code addLabel()} in 
their Element implementations.
+ *
+ * @since 4.0.0
+ */
+public class AddLabelStep<S extends Element> extends SideEffectStep<S>
+        implements Mutating<Event.ElementPropertyChangedEvent>, 
TraversalParent {
+
+    private final String[] labels;
+    private Traversal.Admin<S, String> labelTraversal;
+    private CallbackRegistry<Event.ElementPropertyChangedEvent> 
callbackRegistry;
+
+    public AddLabelStep(final Traversal.Admin traversal, final String label, 
final String... moreLabels) {
+        super(traversal);
+        ElementHelper.validateLabel(label);
+        for (final String l : moreLabels) {
+            ElementHelper.validateLabel(l);
+        }
+        final List<String> allLabels = new ArrayList<>();
+        allLabels.add(label);
+        allLabels.addAll(Arrays.asList(moreLabels));
+        this.labels = allLabels.toArray(new String[0]);
+        this.labelTraversal = null;
+    }
+
+    public AddLabelStep(final Traversal.Admin traversal, final 
Traversal.Admin<S, String> labelTraversal) {
+        super(traversal);
+        this.labels = null;
+        this.labelTraversal = this.integrateChild(labelTraversal);
+    }
+
+    @Override
+    protected void sideEffect(final Traverser.Admin<S> traverser) {
+        final Element element = traverser.get();
+
+        if (this.labelTraversal != null) {
+            final List<String> collectedLabels = new ArrayList<>();
+            TraversalUtil.applyAll(traverser, this.labelTraversal)
+                    .forEachRemaining(label -> {
+                        ElementHelper.validateLabel(label);
+                        collectedLabels.add(label);
+                    });
+            if (!collectedLabels.isEmpty()) {
+                element.addLabel(collectedLabels.get(0),
+                        collectedLabels.subList(1, 
collectedLabels.size()).toArray(new String[0]));
+            }
+        } else {
+            element.addLabel(this.labels[0],
+                    Arrays.copyOfRange(this.labels, 1, this.labels.length));
+        }
+
+        // trigger event callbacks
+        final Optional<EventStrategy> optEventStrategy = 
getTraversal().getStrategies().getStrategy(EventStrategy.class);
+        if (EventUtil.hasAnyCallbacks(callbackRegistry) && 
optEventStrategy.isPresent()) {
+            final EventStrategy es = optEventStrategy.get();
+            EventUtil.registerPropertyChange(callbackRegistry, es, element, 
null, null, new Object[0]);
+        }
+    }
+
+    @Override
+    public CallbackRegistry<Event.ElementPropertyChangedEvent> 
getMutatingCallbackRegistry() {
+        if (null == callbackRegistry) callbackRegistry = new 
ListCallbackRegistry<>();
+        return callbackRegistry;
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return Collections.singleton(TraverserRequirement.OBJECT);
+    }
+
+    @Override
+    public String toString() {
+        return StringFactory.stepString(this, this.labels != null ? 
Arrays.asList(this.labels) : this.labelTraversal);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        if (this.labels != null) result ^= Arrays.hashCode(this.labels);
+        if (this.labelTraversal != null) result ^= 
this.labelTraversal.hashCode();
+        return result;
+    }
+
+    @Override
+    public List<Traversal.Admin<S, String>> getLocalChildren() {
+        return this.labelTraversal != null ? 
Collections.singletonList(this.labelTraversal) : Collections.emptyList();
+    }
+
+    @Override
+    public AddLabelStep<S> clone() {
+        final AddLabelStep<S> clone = (AddLabelStep<S>) super.clone();
+        if (this.labelTraversal != null)
+            clone.labelTraversal = this.labelTraversal.clone();
+        return clone;
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        if (this.labelTraversal != null)
+            this.integrateChild(this.labelTraversal);
+    }
+}
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/DropLabelsStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/DropLabelsStep.java
new file mode 100644
index 0000000000..14e5d0634f
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/DropLabelsStep.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.event.EventUtil;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Side-effect step that removes labels from the current element by calling
+ * {@link Element#dropLabels()} or {@link Element#dropLabel(String, 
String...)}.
+ *
+ * @since 4.0.0
+ */
+public class DropLabelsStep<S extends Element> extends SideEffectStep<S>
+        implements Mutating<Event.ElementPropertyChangedEvent>, 
TraversalParent {
+
+    private final boolean dropAll;
+    private final String[] labels;
+    private Traversal.Admin<S, String> labelTraversal;
+    private CallbackRegistry<Event.ElementPropertyChangedEvent> 
callbackRegistry;
+
+    /**
+     * Constructor for dropLabels() - removes all labels.
+     */
+    public DropLabelsStep(final Traversal.Admin traversal) {
+        super(traversal);
+        this.dropAll = true;
+        this.labels = null;
+        this.labelTraversal = null;
+    }
+
+    /**
+     * Constructor for dropLabel(String, String...) - removes specific labels.
+     */
+    public DropLabelsStep(final Traversal.Admin traversal, final String label, 
final String... moreLabels) {
+        super(traversal);
+        this.dropAll = false;
+        final List<String> allLabels = new ArrayList<>();
+        allLabels.add(label);
+        allLabels.addAll(Arrays.asList(moreLabels));
+        this.labels = allLabels.toArray(new String[0]);
+        this.labelTraversal = null;
+    }
+
+    /**
+     * Constructor for dropLabel(Traversal) - removes dynamically computed 
label.
+     */
+    public DropLabelsStep(final Traversal.Admin traversal, final 
Traversal.Admin<S, String> labelTraversal) {
+        super(traversal);
+        this.dropAll = false;
+        this.labels = null;
+        this.labelTraversal = this.integrateChild(labelTraversal);
+    }
+
+    @Override
+    protected void sideEffect(final Traverser.Admin<S> traverser) {
+        final Element element = traverser.get();
+
+        if (this.labelTraversal != null) {
+            final String label = TraversalUtil.apply(traverser, 
this.labelTraversal);
+            if (label != null) {
+                element.dropLabel(label);
+            }
+        } else if (this.dropAll) {
+            element.dropLabels();
+        } else {
+            element.dropLabel(this.labels[0],
+                    Arrays.copyOfRange(this.labels, 1, this.labels.length));
+        }
+
+        // trigger event callbacks
+        final Optional<EventStrategy> optEventStrategy = 
getTraversal().getStrategies().getStrategy(EventStrategy.class);
+        if (EventUtil.hasAnyCallbacks(callbackRegistry) && 
optEventStrategy.isPresent()) {
+            final EventStrategy es = optEventStrategy.get();
+            EventUtil.registerPropertyChange(callbackRegistry, es, element, 
null, null, new Object[0]);
+        }
+    }
+
+    @Override
+    public CallbackRegistry<Event.ElementPropertyChangedEvent> 
getMutatingCallbackRegistry() {
+        if (null == callbackRegistry) callbackRegistry = new 
ListCallbackRegistry<>();
+        return callbackRegistry;
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return Collections.singleton(TraverserRequirement.OBJECT);
+    }
+
+    @Override
+    public String toString() {
+        if (this.dropAll) return StringFactory.stepString(this);
+        return StringFactory.stepString(this, this.labels != null ? 
Arrays.asList(this.labels) : this.labelTraversal);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result ^= Boolean.hashCode(this.dropAll);
+        if (this.labels != null) result ^= Arrays.hashCode(this.labels);
+        if (this.labelTraversal != null) result ^= 
this.labelTraversal.hashCode();
+        return result;
+    }
+
+    @Override
+    public List<Traversal.Admin<S, String>> getLocalChildren() {
+        return this.labelTraversal != null ? 
Collections.singletonList(this.labelTraversal) : Collections.emptyList();
+    }
+
+    @Override
+    public DropLabelsStep<S> clone() {
+        final DropLabelsStep<S> clone = (DropLabelsStep<S>) super.clone();
+        if (this.labelTraversal != null)
+            clone.labelTraversal = this.labelTraversal.clone();
+        return clone;
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        if (this.labelTraversal != null)
+            this.integrateChild(this.labelTraversal);
+    }
+}
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
index c20c68fde0..7f719bfbed 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
@@ -92,7 +92,14 @@ public class HasContainer implements Serializable, 
Cloneable, Predicate<Element>
     }
 
     protected boolean testLabel(final Element element) {
-        return this.predicate.test(element.label());
+        // Test against all labels for multi-label support.
+        // For single-label elements this is equivalent to testing 
element.label().
+        for (final String label : element.labels()) {
+            if (this.predicate.test(label)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     protected boolean testValue(final Property property) {
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
index 43671c824b..68f89cc615 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
@@ -90,4 +90,16 @@ public class WithOptions {
      * Index items using a {@code LinkedHashMap}.
      */
     public static int map = 1;
+
+    //
+    // Multi-label configuration
+    //
+
+    /**
+     * Configures multi-label behavior for valueMap and elementMap steps.
+     * When enabled, labels are returned as {@code Set<String>} instead of 
{@code String}.
+     *
+     * @since 4.0.0
+     */
+    public static final String multilabel = 
Graph.Hidden.hide("tinkerpop.multilabel");
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Element.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Element.java
index 0b796a1f25..354d945535 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Element.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Element.java
@@ -44,11 +44,28 @@ public abstract interface Element {
      */
     public Object id();
 
+    /**
+     * Gets all labels for this element.
+     * <p>
+     * For {@link Vertex}: may return zero or more labels (multi-label 
support).
+     * For {@link Edge}: returns a singleton set (single label only in 
TinkerGraph).
+     * For {@link VertexProperty}: returns a singleton set containing the 
property key.
+     *
+     * @return An unmodifiable {@link Set} of labels; may be empty for 
vertices with no labels
+     * @since 4.0.0
+     */
+    public default Set<String> labels() {
+        return Collections.singleton(label());
+    }
+
     /**
      * Gets the label for the graph {@code Element} which helps categorize it.
      *
      * @return The label of the element
+     * @deprecated As of release 4.0.0, replaced by {@link #labels()}. This 
method returns an arbitrary label
+     *             when multiple labels exist.
      */
+    @Deprecated
     public String label();
 
     /**
@@ -99,6 +116,41 @@ public abstract interface Element {
      */
     public void remove();
 
+    /**
+     * Adds one or more labels to this element.
+     *
+     * @param label  the first label to add
+     * @param labels additional labels to add
+     * @throws UnsupportedOperationException if the element does not support 
label mutation
+     * @since 4.0.0
+     */
+    public default void addLabel(final String label, final String... labels) {
+        throw Element.Exceptions.labelMutationNotSupported();
+    }
+
+    /**
+     * Removes all labels from this element, triggering the provider's default 
label behavior.
+     *
+     * @throws UnsupportedOperationException if the element does not support 
label mutation
+     * @since 4.0.0
+     */
+    public default void dropLabels() {
+        throw Element.Exceptions.labelMutationNotSupported();
+    }
+
+    /**
+     * Removes specific labels from this element.
+     * If this action removes all labels, triggers the provider's default 
label behavior.
+     *
+     * @param label  the first label to remove
+     * @param labels additional labels to remove
+     * @throws UnsupportedOperationException if the element does not support 
label mutation
+     * @since 4.0.0
+     */
+    public default void dropLabel(final String label, final String... labels) {
+        throw Element.Exceptions.labelMutationNotSupported();
+    }
+
 
     /**
      * Get the values of properties as an {@link Iterator}.
@@ -145,5 +197,9 @@ public abstract interface Element {
         public static IllegalArgumentException labelCanNotBeAHiddenKey(final 
String label) {
             return new IllegalArgumentException("Label can not be a hidden 
key: " + label);
         }
+
+        public static UnsupportedOperationException 
labelMutationNotSupported() {
+            return new UnsupportedOperationException("Label mutation is not 
supported on this element");
+        }
     }
 }
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 962140adb5..d9ab208937 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
@@ -21,7 +21,9 @@ package org.apache.tinkerpop.gremlin.structure;
 import 
org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyVertexProperty;
 
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.Set;
 
 /**
  * A {@code VertexProperty} is similar to a {@link Property} in that it 
denotes a key/value pair associated with an
@@ -78,6 +80,18 @@ public interface VertexProperty<V> extends Property<V>, 
Element {
         return this.key();
     }
 
+    /**
+     * Returns a singleton set containing the property key, consistent with 
the single-label
+     * semantics of {@link VertexProperty}.
+     *
+     * @return a singleton {@link Set} containing this property's key
+     * @since 4.0.0
+     */
+    @Override
+    public default Set<String> labels() {
+        return Collections.singleton(this.key());
+    }
+
     /**
      * Constructs an empty {@code VertexProperty}.
      */
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
index 8c16ce2b80..cc932f8057 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
@@ -29,7 +29,9 @@ import 
org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertexProper
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 
 /**
@@ -43,11 +45,19 @@ public class VertexSerializer extends 
SimpleTypeSerializer<Vertex> {
     @Override
     protected Vertex readValue(final Buffer buffer, final GraphBinaryReader 
context) throws IOException {
         final Object id = context.read(buffer);
-        // reading single string value for now according to GraphBinaryV4
-        final String label = (String) context.readValue(buffer, List.class, 
false).get(0);
+        // Read labels as List<String> for multi-label support
+        final List<String> labelList = context.readValue(buffer, List.class, 
false);
         final List<DetachedVertexProperty> properties = context.read(buffer);
 
-        final DetachedVertex.Builder builder = 
DetachedVertex.build().setId(id).setLabel(label);
+        final DetachedVertex.Builder builder = 
DetachedVertex.build().setId(id);
+
+        if (labelList != null && !labelList.isEmpty()) {
+            if (labelList.size() == 1) {
+                builder.setLabel(labelList.get(0));
+            } else {
+                builder.setLabels(new LinkedHashSet<>(labelList));
+            }
+        }
 
         if (properties != null) {
             for (final DetachedVertexProperty vp : properties) {
@@ -61,11 +71,12 @@ public class VertexSerializer extends 
SimpleTypeSerializer<Vertex> {
     @Override
     protected void writeValue(final Vertex value, final Buffer buffer, final 
GraphBinaryWriter context) throws IOException {
         context.write(value.id(), buffer);
-        // wrapping label into list here for now according to GraphBinaryV4, 
but we aren't allowing null label yet
-        if (value.label() == null) {
-            throw new IOException("Unexpected null value when nullable is 
false");
+        // Write all labels as List<String> for multi-label support.
+        final java.util.Set<String> labels = value.labels();
+        if (labels == null || labels.isEmpty()) {
+            throw new IOException("Unexpected null or empty labels when 
nullable is false");
         }
-        context.writeValue(Collections.singletonList(value.label()), buffer, 
false);
+        context.writeValue(new ArrayList<>(labels), buffer, false);
         if (value instanceof ReferenceVertex) {
             context.write(null, buffer);
         }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV4.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV4.java
index 7bfff073bc..43c24d7270 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV4.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV4.java
@@ -86,7 +86,7 @@ class GraphSONSerializersV4 {
             jsonGenerator.writeStartObject();
 
             jsonGenerator.writeObjectField(GraphSONTokens.ID, vertex.id());
-            writeLabel(jsonGenerator, GraphSONTokens.LABEL, vertex.label());
+            writeLabels(jsonGenerator, GraphSONTokens.LABEL, vertex.labels());
             writeTypeForGraphObjectIfUntyped(jsonGenerator, typeInfo, 
GraphSONTokens.VERTEX);
             writeProperties(vertex, jsonGenerator, serializerProvider);
 
@@ -383,8 +383,14 @@ class GraphSONSerializersV4 {
                     v.setId(deserializationContext.readValue(jsonParser, 
Object.class));
                 } else if 
(jsonParser.getCurrentName().equals(GraphSONTokens.LABEL)) {
                     jsonParser.nextToken();
+                    final java.util.Set<String> labels = new 
java.util.LinkedHashSet<>();
                     while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
-                        v.setLabel(jsonParser.getText());
+                        labels.add(jsonParser.getText());
+                    }
+                    if (labels.size() == 1) {
+                        v.setLabel(labels.iterator().next());
+                    } else if (!labels.isEmpty()) {
+                        v.setLabels(labels);
                     }
                 } else if 
(jsonParser.getCurrentName().equals(GraphSONTokens.PROPERTIES)) {
                     jsonParser.nextToken();
@@ -647,4 +653,16 @@ class GraphSONSerializersV4 {
         jsonGenerator.writeString(labelValue);
         jsonGenerator.writeEndArray();
     }
+
+    /**
+     * Helper method for writing multiple labels as an array. Used for 
multi-label vertex support.
+     */
+    private static void writeLabels(final JsonGenerator jsonGenerator, final 
String labelName, final java.util.Set<String> labels) throws IOException {
+        jsonGenerator.writeFieldName(labelName);
+        jsonGenerator.writeStartArray();
+        for (final String label : labels) {
+            jsonGenerator.writeString(label);
+        }
+        jsonGenerator.writeEndArray();
+    }
 }
\ No newline at end of file
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
index 40dc7014c3..68a56905e3 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
@@ -30,9 +30,11 @@ import org.javatuples.Pair;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -245,6 +247,41 @@ public final class ElementHelper {
         return Optional.empty();
     }
 
+    /**
+     * Extracts the value of the {@link T#label} key from the list of 
arguments as a {@link Set} of labels.
+     * Supports both single {@link String} values and {@link 
java.util.Collection} values for multi-label vertices.
+     *
+     * @param keyValues a list of key/value pairs
+     * @return the labels associated with {@link T#label}, or empty if not 
present
+     * @since 4.0.0
+     */
+    public static Optional<Set<String>> getLabelsValue(final Object... 
keyValues) {
+        for (int i = 0; i < keyValues.length; i = i + 2) {
+            if (keyValues[i].equals(T.label)) {
+                final Object labelValue = keyValues[i + 1];
+                if (labelValue instanceof String) {
+                    ElementHelper.validateLabel((String) labelValue);
+                    final Set<String> labels = new LinkedHashSet<>();
+                    labels.add((String) labelValue);
+                    return Optional.of(labels);
+                } else if (labelValue instanceof Collection) {
+                    final Set<String> labels = new LinkedHashSet<>();
+                    for (final Object l : (Collection<?>) labelValue) {
+                        if (!(l instanceof String)) {
+                            throw new IllegalArgumentException("T.label 
collection must contain only Strings");
+                        }
+                        ElementHelper.validateLabel((String) l);
+                        labels.add((String) l);
+                    }
+                    return Optional.of(labels);
+                } else {
+                    throw new IllegalArgumentException("T.label value must be 
String or Collection<String>");
+                }
+            }
+        }
+        return Optional.empty();
+    }
+
     /**
      * Assign key/value pairs as properties to an {@link Element}.  If the 
value of {@link T#id} or
      * {@link T#label} is in the set of pairs, then they are ignored.
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/detached/DetachedVertex.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/detached/DetachedVertex.java
index f331d8da50..57f87059d7 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/detached/DetachedVertex.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/detached/DetachedVertex.java
@@ -32,8 +32,10 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Represents a {@link Vertex} that is disconnected from a {@link Graph}.  
"Disconnection" can mean detachment from
@@ -53,11 +55,24 @@ public class DetachedVertex extends DetachedElement<Vertex> 
implements Vertex {
     private static final String VALUE = "value";
     private static final String PROPERTIES = "properties";
 
+    private Set<String> vertexLabels;
+
     private DetachedVertex() {}
 
     protected DetachedVertex(final Vertex vertex, final boolean 
withProperties) {
         super(vertex);
 
+        // Capture all labels from the source vertex for multi-label support.
+        // super(vertex) only stores element.label() (single label) in 
DetachedElement.
+        try {
+            final Set<String> srcLabels = vertex.labels();
+            if (srcLabels.size() > 1) {
+                this.vertexLabels = new LinkedHashSet<>(srcLabels);
+            }
+        } catch (UnsupportedOperationException e) {
+            // Adjacent vertices in graph computer context may not support 
labels()
+        }
+
         // only serialize properties if requested, and there are meta 
properties present. this prevents unnecessary
         // object creation of a new HashMap of a new HashMap which will just 
be empty.  it will use
         // Collections.emptyMap() by default
@@ -99,6 +114,23 @@ public class DetachedVertex extends DetachedElement<Vertex> 
implements Vertex {
         }
     }
 
+    @Override
+    public Set<String> labels() {
+        if (this.vertexLabels != null) {
+            return Collections.unmodifiableSet(this.vertexLabels);
+        }
+        // Fall back to single label from parent
+        return this.label != null ? Collections.singleton(this.label) : 
Collections.emptySet();
+    }
+
+    @Override
+    public String label() {
+        if (this.vertexLabels != null && !this.vertexLabels.isEmpty()) {
+            return this.vertexLabels.iterator().next();
+        }
+        return this.label != null ? this.label : Vertex.DEFAULT_LABEL;
+    }
+
     @Override
     public <V> VertexProperty<V> property(final String key, final V value) {
         throw Element.Exceptions.propertyAdditionNotSupported();
@@ -196,6 +228,14 @@ public class DetachedVertex extends 
DetachedElement<Vertex> implements Vertex {
             return this;
         }
 
+        public Builder setLabels(final Set<String> labels) {
+            v.vertexLabels = labels != null ? new LinkedHashSet<>(labels) : 
null;
+            if (labels != null && !labels.isEmpty()) {
+                v.label = labels.iterator().next();
+            }
+            return this;
+        }
+
         public DetachedVertex create() {
             return v;
         }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/reference/ReferenceVertex.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/reference/ReferenceVertex.java
index c6e646a563..006f2fd49f 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/reference/ReferenceVertex.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/reference/ReferenceVertex.java
@@ -28,12 +28,16 @@ import 
org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
 public class ReferenceVertex extends ReferenceElement<Vertex> implements 
Vertex {
 
+    private Set<String> vertexLabels;
+
     private ReferenceVertex() {
 
     }
@@ -46,8 +50,39 @@ public class ReferenceVertex extends 
ReferenceElement<Vertex> implements Vertex
         super(id, label);
     }
 
+    public ReferenceVertex(final Object id, final Set<String> labels) {
+        super(id, labels != null && !labels.isEmpty() ? 
labels.iterator().next() : Vertex.DEFAULT_LABEL);
+        this.vertexLabels = labels != null ? new LinkedHashSet<>(labels) : 
null;
+    }
+
     public ReferenceVertex(final Vertex vertex) {
         super(vertex);
+        // Capture all labels from the source vertex for multi-label support.
+        // super(vertex) only stores element.label() (single label) in 
ReferenceElement.
+        try {
+            final Set<String> srcLabels = vertex.labels();
+            if (srcLabels.size() > 1) {
+                this.vertexLabels = new LinkedHashSet<>(srcLabels);
+            }
+        } catch (UnsupportedOperationException e) {
+            // Adjacent vertices in graph computer context may not support 
labels()
+        }
+    }
+
+    @Override
+    public Set<String> labels() {
+        if (this.vertexLabels != null) {
+            return Collections.unmodifiableSet(this.vertexLabels);
+        }
+        return this.label != null ? Collections.singleton(this.label) : 
Collections.emptySet();
+    }
+
+    @Override
+    public String label() {
+        if (this.vertexLabels != null && !this.vertexLabels.isEmpty()) {
+            return this.vertexLabels.iterator().next();
+        }
+        return this.label != null ? this.label : Vertex.DEFAULT_LABEL;
     }
 
     @Override
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
index 9b3de0bd34..a8e1dc593d 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
@@ -732,6 +732,11 @@ public final class StarGraph implements Graph, 
Serializable {
             throw GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead();
         }
 
+        @Override
+        public Set<String> labels() {
+            throw GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead();
+        }
+
         @Override
         public Graph graph() {
             return StarGraph.this;
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 
b/gremlin-language/src/main/antlr4/Gremlin.g4
index ef19a6206c..54bbff520d 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -118,6 +118,7 @@ traversalSourceSpawnMethod_addE
 traversalSourceSpawnMethod_addV
     : K_ADDV LPAREN RPAREN
     | K_ADDV LPAREN stringArgument RPAREN
+    | K_ADDV LPAREN stringArgument COMMA stringArgument (COMMA 
stringArgument)* RPAREN
     | K_ADDV LPAREN nestedTraversal RPAREN
     ;
 
@@ -309,6 +310,10 @@ traversalMethod
     | traversalMethod_dateAdd
     | traversalMethod_dateDiff
     | traversalMethod_asNumber
+    | traversalMethod_labels
+    | traversalMethod_addLabel
+    | traversalMethod_dropLabels
+    | traversalMethod_dropLabel
     ;
 
 traversalMethod_V
@@ -327,6 +332,7 @@ traversalMethod_addE
 traversalMethod_addV
     : K_ADDV LPAREN RPAREN #traversalMethod_addV_Empty
     | K_ADDV LPAREN stringArgument RPAREN #traversalMethod_addV_String
+    | K_ADDV LPAREN stringArgument COMMA stringArgument (COMMA 
stringArgument)* RPAREN #traversalMethod_addV_StringVarargs
     | K_ADDV LPAREN nestedTraversal RPAREN #traversalMethod_addV_Traversal
     ;
 
@@ -622,6 +628,24 @@ traversalMethod_label
     : K_LABEL LPAREN RPAREN
     ;
 
+traversalMethod_labels
+    : K_LABELS LPAREN RPAREN
+    ;
+
+traversalMethod_addLabel
+    : K_ADDLABEL LPAREN stringArgument (COMMA stringArgument)* RPAREN 
#traversalMethod_addLabel_String
+    | K_ADDLABEL LPAREN nestedTraversal RPAREN 
#traversalMethod_addLabel_Traversal
+    ;
+
+traversalMethod_dropLabels
+    : K_DROPLABELS LPAREN RPAREN #traversalMethod_dropLabels_Empty
+    ;
+
+traversalMethod_dropLabel
+    : K_DROPLABEL LPAREN stringArgument (COMMA stringArgument)* RPAREN 
#traversalMethod_dropLabel_String
+    | K_DROPLABEL LPAREN nestedTraversal RPAREN 
#traversalMethod_dropLabel_Traversal
+    ;
+
 traversalMethod_length
     : K_LENGTH LPAREN RPAREN #traversalMethod_length_Empty
     | K_LENGTH LPAREN traversalScope RPAREN #traversalMethod_length_Scope
@@ -1737,6 +1761,7 @@ keyword
     : TRAVERSAL_ROOT // g - __ is not an allowable key in this context
     | K_ADDALL
     | K_ADDE
+    | K_ADDLABEL
     | K_ADDV
     | K_AGGREGATE
     | K_ALL
@@ -1807,6 +1832,8 @@ keyword
     | K_DOUBLE
     | K_DOUBLEU
     | K_DROP
+    | K_DROPLABEL
+    | K_DROPLABELS
     | K_DT
     | K_DURATION
     | K_DURATIONU
@@ -2047,6 +2074,7 @@ keyword
 
 K_ADDALL: 'addAll';
 K_ADDE: 'addE';
+K_ADDLABEL: 'addLabel';
 K_ADDV: 'addV';
 K_AGGREGATE: 'aggregate';
 K_ALL: 'all';
@@ -2117,6 +2145,8 @@ K_DIV: 'div';
 K_DOUBLE: 'double';
 K_DOUBLEU: 'DOUBLE';
 K_DROP: 'drop';
+K_DROPLABEL: 'dropLabel';
+K_DROPLABELS: 'dropLabels';
 K_DT: 'DT';
 K_DURATION: 'duration';
 K_DURATIONU: 'DURATION';
diff --git 
a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TypeSerializerFailureTests.java
 
b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TypeSerializerFailureTests.java
index e4afbbe308..96254a57da 100644
--- 
a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TypeSerializerFailureTests.java
+++ 
b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/TypeSerializerFailureTests.java
@@ -57,7 +57,7 @@ public class TypeSerializerFailureTests {
 
     @Parameterized.Parameters(name = "Value={0}")
     public static Collection input() {
-        final ReferenceVertex vertex = new ReferenceVertex("a vertex", null);
+        final ReferenceVertex vertex = new ReferenceVertex("a vertex", 
(String) null);
 
         final BulkSet<Object> bulkSet = new BulkSet<>();
         bulkSet.add(vertex, 1L);
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
index 9a361793e6..197b43e987 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/AbstractTinkerGraph.java
@@ -283,6 +283,17 @@ public abstract class AbstractTinkerGraph implements Graph 
{
 
     protected abstract void addInEdge(final TinkerVertex vertex, final String 
label, final Edge edge);
 
+    /**
+     * Called when a vertex's labels are modified to allow the graph to update 
any internal label indices.
+     * The default implementation is a no-op since TinkerGraph does not 
maintain a separate label index.
+     *
+     * @param vertex the vertex whose labels have changed
+     * @since 4.0.0
+     */
+    public void updateVertexLabelIndex(final TinkerVertex vertex) {
+        // no-op by default - TinkerGraph does not maintain a separate label 
index
+    }
+
     protected TinkerVertex createTinkerVertex(final Object id, final String 
label, final AbstractTinkerGraph graph) {
         return new TinkerVertex(id, label, graph);
     }
@@ -291,6 +302,19 @@ public abstract class AbstractTinkerGraph implements Graph 
{
         return new TinkerVertex(id, label, graph, currentVersion);
     }
 
+    /**
+     * Creates a TinkerVertex with multiple labels.
+     *
+     * @param id     the vertex id
+     * @param labels the set of labels for the vertex
+     * @param graph  the graph instance
+     * @return a new TinkerVertex
+     * @since 4.0.0
+     */
+    protected TinkerVertex createTinkerVertex(final Object id, final 
Set<String> labels, final AbstractTinkerGraph graph) {
+        return new TinkerVertex(id, labels, graph);
+    }
+
     protected TinkerEdge createTinkerEdge(final Object id, final Vertex 
outVertex, final String label, final Vertex inVertex) {
         return new TinkerEdge(id, outVertex, label, inVertex);
     }
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
index 43fcc29d13..dbe16e80b0 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
@@ -105,6 +105,11 @@ public class TinkerEdge extends TinkerElement implements 
Edge {
         return null == this.properties ? Collections.emptySet() : 
this.properties.keySet();
     }
 
+    @Override
+    public Set<String> labels() {
+        return Collections.singleton(this.label);
+    }
+
     @Override
     public void remove() {
         graph.touch(this);
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index b5bcfd9f9a..ef1f0f846e 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -135,7 +135,8 @@ public class TinkerGraph extends AbstractTinkerGraph {
     public Vertex addVertex(final Object... keyValues) {
         ElementHelper.legalPropertyKeyValueArray(keyValues);
         Object idValue = 
vertexIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
-        final String label = 
ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL);
+        final Set<String> labels = 
ElementHelper.getLabelsValue(keyValues).orElse(
+                Collections.singleton(Vertex.DEFAULT_LABEL));
 
         if (null != idValue) {
             if (this.vertices.containsKey(idValue))
@@ -144,7 +145,7 @@ public class TinkerGraph extends AbstractTinkerGraph {
             idValue = vertexIdManager.getNextId(this);
         }
 
-        final Vertex vertex = createTinkerVertex(idValue, label, this);
+        final Vertex vertex = createTinkerVertex(idValue, labels, this);
         ElementHelper.attachProperties(vertex, 
VertexProperty.Cardinality.list, keyValues);
         this.vertices.put(vertex.id(), vertex);
 
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
index 80a0f80642..ecbac567bd 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
@@ -31,6 +31,7 @@ import 
org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -54,11 +55,18 @@ public class TinkerVertex extends TinkerElement implements 
Vertex {
     private boolean allowNullPropertyValues;
     private final boolean isTxMode;
 
+    /**
+     * Multi-label storage for this vertex. Shadows the single-label field in 
TinkerElement.
+     */
+    protected final Set<String> vertexLabels;
+
     protected TinkerVertex(final Object id, final String label, final 
AbstractTinkerGraph graph) {
         super(id, label);
         this.graph = graph;
         this.isTxMode = graph instanceof TinkerTransactionGraph;
         this.allowNullPropertyValues = 
graph.features().vertex().supportsNullPropertyValues();
+        this.vertexLabels = new LinkedHashSet<>();
+        this.vertexLabels.add(null == label ? Vertex.DEFAULT_LABEL : label);
     }
 
     protected TinkerVertex(final Object id, final String label, final 
AbstractTinkerGraph graph, final long currentVersion) {
@@ -66,12 +74,78 @@ public class TinkerVertex extends TinkerElement implements 
Vertex {
         this.graph = graph;
         this.isTxMode = graph instanceof TinkerTransactionGraph;
         this.allowNullPropertyValues = 
graph.features().vertex().supportsNullPropertyValues();
+        this.vertexLabels = new LinkedHashSet<>();
+        this.vertexLabels.add(null == label ? Vertex.DEFAULT_LABEL : label);
+    }
+
+    /**
+     * Constructs a TinkerVertex with multiple labels.
+     */
+    protected TinkerVertex(final Object id, final Set<String> labels, final 
AbstractTinkerGraph graph) {
+        super(id, (labels == null || labels.isEmpty()) ? Vertex.DEFAULT_LABEL 
: labels.iterator().next());
+        this.graph = graph;
+        this.isTxMode = graph instanceof TinkerTransactionGraph;
+        this.allowNullPropertyValues = 
graph.features().vertex().supportsNullPropertyValues();
+        if (labels == null || labels.isEmpty()) {
+            this.vertexLabels = new LinkedHashSet<>();
+            this.vertexLabels.add(Vertex.DEFAULT_LABEL);
+        } else {
+            this.vertexLabels = new LinkedHashSet<>(labels);
+        }
+    }
+
+    @Override
+    public Set<String> labels() {
+        return Collections.unmodifiableSet(this.vertexLabels);
+    }
+
+    @Override
+    @Deprecated
+    public String label() {
+        return this.vertexLabels.iterator().next();
+    }
+
+    @Override
+    public void addLabel(final String label, final String... labels) {
+        ElementHelper.validateLabel(label);
+        for (final String l : labels) {
+            ElementHelper.validateLabel(l);
+        }
+
+        // Remove default label if it was the only label and we're adding real 
labels
+        if (this.vertexLabels.size() == 1 && 
this.vertexLabels.contains(Vertex.DEFAULT_LABEL)) {
+            this.vertexLabels.remove(Vertex.DEFAULT_LABEL);
+        }
+
+        this.vertexLabels.add(label);
+        Collections.addAll(this.vertexLabels, labels);
+        this.graph.updateVertexLabelIndex(this);
+    }
+
+    @Override
+    public void dropLabels() {
+        this.vertexLabels.clear();
+        this.vertexLabels.add(Vertex.DEFAULT_LABEL);
+        this.graph.updateVertexLabelIndex(this);
+    }
+
+    @Override
+    public void dropLabel(final String label, final String... labels) {
+        this.vertexLabels.remove(label);
+        for (final String l : labels) {
+            this.vertexLabels.remove(l);
+        }
+
+        if (this.vertexLabels.isEmpty()) {
+            this.vertexLabels.add(Vertex.DEFAULT_LABEL);
+        }
+        this.graph.updateVertexLabelIndex(this);
     }
 
     @Override
     public Object clone() {
         if (!isTxMode) {
-            final TinkerVertex vertex = new TinkerVertex(id, label, graph, 
currentVersion);
+            final TinkerVertex vertex = new TinkerVertex(id, new 
LinkedHashSet<>(vertexLabels), graph);
             vertex.inEdgesId = inEdgesId;
             vertex.outEdgesId = outEdgesId;
             vertex.properties = properties;
@@ -79,6 +153,8 @@ public class TinkerVertex extends TinkerElement implements 
Vertex {
         }
 
         final TinkerVertex vertex = new TinkerVertex(id, label, graph, 
currentVersion);
+        vertex.vertexLabels.clear();
+        vertex.vertexLabels.addAll(this.vertexLabels);
         if (inEdgesId != null)
             vertex.inEdgesId = CollectionUtil.clone((ConcurrentHashMap<String, 
Set<Object>>) inEdgesId);
 
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/LabelsStepTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/LabelsStepTest.java
new file mode 100644
index 0000000000..9c816cf9c8
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/LabelsStepTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map;
+
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for the labels() traversal step.
+ */
+public class LabelsStepTest {
+
+    private Graph graph;
+    private GraphTraversalSource g;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        graph.close();
+    }
+
+    @Test
+    public void shouldStreamAllLabelsFromVertex() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final List<String> labels = g.V(v).labels().toList();
+        assertThat(labels, hasSize(2));
+        assertThat(labels, containsInAnyOrder("person", "employee"));
+    }
+
+    @Test
+    public void shouldCountLabelsCorrectly() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final long count = g.V(v).labels().count().next();
+        assertThat(count, is(2L));
+    }
+
+    @Test
+    public void shouldFoldAllLabels() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final List<String> folded = g.V(v).labels().fold().next();
+        assertThat(folded, hasSize(2));
+        assertThat(folded, containsInAnyOrder("person", "employee"));
+    }
+
+    @Test
+    public void shouldReturnSingletonForEdge() {
+        final Vertex v1 = g.addV("person").next();
+        final Vertex v2 = g.addV("person").next();
+        final Edge e = v1.addEdge("knows", v2);
+        final List<String> labels = g.E(e).labels().toList();
+        assertThat(labels, hasSize(1));
+        assertThat(labels, containsInAnyOrder("knows"));
+    }
+
+    @Test
+    public void shouldStreamSingleLabelFromSingleLabelVertex() {
+        final Vertex v = g.addV("person").next();
+        final List<String> labels = g.V(v).labels().toList();
+        assertThat(labels, hasSize(1));
+        assertThat(labels, containsInAnyOrder("person"));
+    }
+
+    @Test
+    public void shouldStreamDefaultLabelFromDefaultVertex() {
+        final Vertex v = g.addV().next();
+        final List<String> labels = g.V(v).labels().toList();
+        assertThat(labels, hasSize(1));
+        assertThat(labels, containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+}
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/MergeVMultiLabelTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/MergeVMultiLabelTest.java
new file mode 100644
index 0000000000..2257a15b60
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/MergeVMultiLabelTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for mergeV() multi-label support.
+ */
+public class MergeVMultiLabelTest {
+
+    private Graph graph;
+    private GraphTraversalSource g;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        graph.close();
+    }
+
+    // --- mergeV create with multi-label ---
+
+    @Test
+    public void shouldCreateVertexWithMultiLabelViaMergeV() {
+        final Set<String> labels = new LinkedHashSet<>(Arrays.asList("person", 
"employee"));
+        final Vertex v = g.mergeV(Map.of(T.label, labels, "name", 
"marko")).next();
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("person", "employee"));
+        assertThat(v.value("name"), is("marko"));
+    }
+
+    @Test
+    public void shouldCreateVertexWithSingleLabelViaMergeV() {
+        final Vertex v = g.mergeV(Map.of(T.label, "person", "name", 
"marko")).next();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("person"));
+    }
+
+    @Test
+    public void shouldCreateVertexWithListLabelViaMergeV() {
+        final List<String> labels = Arrays.asList("a", "b", "c");
+        final Vertex v = g.mergeV(Map.of(T.label, labels, "name", 
"test")).next();
+        assertThat(v.labels(), hasSize(3));
+        assertThat(v.labels(), containsInAnyOrder("a", "b", "c"));
+    }
+
+    // --- mergeV match with multi-label ---
+
+    @Test
+    public void shouldMatchVertexWithMultiLabelViaMergeV() {
+        // create a vertex with two labels
+        final Vertex existing = 
g.addV("person").addLabel("employee").property("name", "marko").next();
+
+        // mergeV should match it using AND semantics
+        final Set<String> labels = new LinkedHashSet<>(Arrays.asList("person", 
"employee"));
+        final Vertex matched = g.mergeV(Map.of(T.label, labels, "name", 
"marko")).next();
+
+        assertThat(matched.id(), is(existing.id()));
+        // should not create a new vertex
+        assertThat(g.V().count().next(), is(1L));
+    }
+
+    @Test
+    public void shouldNotMatchWhenVertexMissingOneLabel() {
+        // create a vertex with only one label
+        g.addV("person").property("name", "marko").next();
+
+        // mergeV with two labels should NOT match (AND semantics)
+        final Set<String> labels = new LinkedHashSet<>(Arrays.asList("person", 
"employee"));
+        g.mergeV(Map.of(T.label, labels, "name", "marko")).next();
+
+        // should have created a new vertex
+        assertThat(g.V().count().next(), is(2L));
+    }
+
+    // --- mergeV onMatch label replacement ---
+
+    @Test
+    public void shouldReplaceLabelsOnMatchWithSingleLabel() {
+        final Vertex v = 
g.addV("person").addLabel("employee").property("name", "marko").next();
+
+        g.mergeV(Map.of(T.label, "person", "name", "marko"))
+                .option(Merge.onMatch, Map.of(T.label, "manager")).next();
+
+        // labels should be wholly replaced
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("manager"));
+    }
+
+    @Test
+    public void shouldReplaceLabelsOnMatchWithMultiLabel() {
+        final Vertex v = g.addV("person").property("name", "marko").next();
+
+        final Set<String> newLabels = new 
LinkedHashSet<>(Arrays.asList("manager", "director"));
+        g.mergeV(Map.of(T.label, "person", "name", "marko"))
+                .option(Merge.onMatch, Map.of(T.label, newLabels)).next();
+
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("manager", "director"));
+    }
+
+    @Test
+    public void shouldApplyDefaultLabelOnMatchWithEmptyCollection() {
+        final Vertex v = g.addV("person").property("name", "marko").next();
+
+        g.mergeV(Map.of(T.label, "person", "name", "marko"))
+                .option(Merge.onMatch, Map.of(T.label, 
Collections.emptySet())).next();
+
+        // empty collection triggers default label behavior
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+}
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/LabelMutationPropertyTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/LabelMutationPropertyTest.java
new file mode 100644
index 0000000000..5aeea9daa8
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/LabelMutationPropertyTest.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package 
org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.sideEffect;
+
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Property-based tests for addLabel() and dropLabel() traversal steps.
+ * Uses randomized inputs to validate universal correctness properties.
+ */
+public class LabelMutationPropertyTest {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(LabelMutationPropertyTest.class);
+    private static final int ITERATIONS = 100;
+    private static final String LABEL_CHARS = "abcdefghijklmnopqrstuvwxyz";
+
+    private Graph graph;
+    private GraphTraversalSource g;
+    private Random random;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+        random = new Random(42); // deterministic seed for reproducibility
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        graph.close();
+    }
+
+    private String randomLabel() {
+        final int len = 1 + random.nextInt(8);
+        final StringBuilder sb = new StringBuilder(len);
+        for (int i = 0; i < len; i++) {
+            
sb.append(LABEL_CHARS.charAt(random.nextInt(LABEL_CHARS.length())));
+        }
+        return sb.toString();
+    }
+
+    private Set<String> randomLabelSet(final int minSize, final int maxSize) {
+        final int size = minSize + random.nextInt(maxSize - minSize + 1);
+        final Set<String> labels = new HashSet<>();
+        while (labels.size() < size) {
+            labels.add(randomLabel());
+        }
+        return labels;
+    }
+
+    /**
+     * Property 4: AddLabel idempotence.
+     * For any vertex and any label L, if L is already in the vertex's label 
set,
+     * calling addLabel(L) shall result in the label set being unchanged.
+     */
+    @Test
+    public void shouldBeIdempotentWhenAddingExistingLabel() {
+        for (int i = 0; i < ITERATIONS; i++) {
+            final Set<String> initialLabels = randomLabelSet(1, 5);
+            final Vertex v = g.addV(initialLabels.iterator().next()).next();
+            // add remaining labels
+            for (final String l : initialLabels) {
+                g.V(v).addLabel(l).iterate();
+            }
+
+            // pick a label already present
+            final List<String> labelList = new ArrayList<>(v.labels());
+            final String existingLabel = 
labelList.get(random.nextInt(labelList.size()));
+            final Set<String> beforeAdd = new HashSet<>(v.labels());
+
+            // add it again - should be idempotent
+            g.V(v).addLabel(existingLabel).iterate();
+            final Set<String> afterAdd = new HashSet<>(v.labels());
+
+            assertThat("Iteration " + i + ": addLabel should be idempotent for 
label '" + existingLabel + "'",
+                    afterAdd, is(beforeAdd));
+        }
+    }
+
+    /**
+     * Property 6: DropLabels removes all labels and applies default.
+     * For any TinkerVertex with any number of labels, calling dropLabels()
+     * shall result in the vertex having only the default label "vertex".
+     */
+    @Test
+    public void shouldApplyDefaultLabelAfterDroppingAllLabels() {
+        for (int i = 0; i < ITERATIONS; i++) {
+            final Set<String> initialLabels = randomLabelSet(1, 5);
+            final Vertex v = g.addV(initialLabels.iterator().next()).next();
+            for (final String l : initialLabels) {
+                g.V(v).addLabel(l).iterate();
+            }
+
+            // drop all labels
+            g.V(v).dropLabels().iterate();
+
+            assertThat("Iteration " + i + ": after dropLabels(), vertex should 
have exactly one label",
+                    v.labels(), hasSize(1));
+            assertThat("Iteration " + i + ": after dropLabels(), vertex should 
have default label",
+                    v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+        }
+    }
+
+    /**
+     * Property 8: DropLabel non-existent labels no-op.
+     * For any vertex and any label L not present in the vertex's label set,
+     * calling dropLabel(L) shall leave the label set unchanged.
+     */
+    @Test
+    public void shouldBeNoOpWhenDroppingNonExistentLabel() {
+        for (int i = 0; i < ITERATIONS; i++) {
+            final Set<String> initialLabels = randomLabelSet(1, 5);
+            final Vertex v = g.addV(initialLabels.iterator().next()).next();
+            for (final String l : initialLabels) {
+                g.V(v).addLabel(l).iterate();
+            }
+
+            // generate a label guaranteed not to be in the set
+            String nonExistent = randomLabel();
+            while (v.labels().contains(nonExistent)) {
+                nonExistent = randomLabel();
+            }
+
+            final Set<String> beforeDrop = new HashSet<>(v.labels());
+            g.V(v).dropLabel(nonExistent).iterate();
+            final Set<String> afterDrop = new HashSet<>(v.labels());
+
+            assertThat("Iteration " + i + ": dropLabel of non-existent label 
'" + nonExistent + "' should be no-op",
+                    afterDrop, is(beforeDrop));
+        }
+    }
+
+    /**
+     * Property 12: ValueMap/ElementMap multilabel configuration.
+     * For any element with labels L, 
valueMap(true).with(WithOptions.multilabel) shall return
+     * labels as Set&lt;String&gt; equal to L, and without the config shall 
return a single String from L.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void shouldReturnCorrectLabelTypeBasedOnMultilabelConfig() {
+        for (int i = 0; i < ITERATIONS; i++) {
+            final Set<String> initialLabels = randomLabelSet(1, 4);
+            final Vertex v = g.addV(initialLabels.iterator().next()).next();
+            for (final String l : initialLabels) {
+                g.V(v).addLabel(l).iterate();
+            }
+
+            // with multilabel config: should return Set<String>
+            final Map<Object, Object> mapWithConfig = g.V(v).valueMap(true)
+                    .with(WithOptions.multilabel).next();
+            final Object labelWithConfig = mapWithConfig.get(T.label);
+            assertThat("Iteration " + i + ": with multilabel config, label 
should be a Set",
+                    labelWithConfig, instanceOf(Set.class));
+            assertThat("Iteration " + i + ": with multilabel config, labels 
should match",
+                    (Set<String>) labelWithConfig, is(v.labels()));
+
+            // without multilabel config: should return single String
+            final Map<Object, Object> mapWithoutConfig = 
g.V(v).valueMap(true).next();
+            final Object labelWithoutConfig = mapWithoutConfig.get(T.label);
+            assertThat("Iteration " + i + ": without multilabel config, label 
should be a String",
+                    labelWithoutConfig, instanceOf(String.class));
+            assertThat("Iteration " + i + ": without multilabel config, label 
should be in vertex labels",
+                    v.labels().contains((String) labelWithoutConfig), 
is(true));
+        }
+    }
+}
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/LabelMutationStepTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/LabelMutationStepTest.java
new file mode 100644
index 0000000000..58ff4fbce1
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/LabelMutationStepTest.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package 
org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.sideEffect;
+
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.constant;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for addLabel() and dropLabel() traversal steps, addV multi-label,
+ * and valueMap/elementMap multilabel configuration.
+ */
+public class LabelMutationStepTest {
+
+    private Graph graph;
+    private GraphTraversalSource g;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        graph.close();
+    }
+
+    // --- addLabel step tests ---
+
+    @Test
+    public void shouldAddLabelViaTraversal() {
+        final Vertex v = g.addV("person").next();
+        g.V(v).addLabel("employee").iterate();
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("person", "employee"));
+    }
+
+    @Test
+    public void shouldAddLabelWithConstantTraversal() {
+        final Vertex v = g.addV("person").next();
+        g.V(v).addLabel(constant("manager")).iterate();
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("person", "manager"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowWhenAddingLabelToEdgeViaTraversal() {
+        final Vertex v1 = g.addV("person").next();
+        final Vertex v2 = g.addV("person").next();
+        v1.addEdge("knows", v2);
+        g.E().addLabel("friend").iterate();
+    }
+
+    // --- dropLabel step tests ---
+
+    @Test
+    public void shouldDropSpecificLabelViaTraversal() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        g.V(v).dropLabel("person").iterate();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("employee"));
+    }
+
+    @Test
+    public void shouldDropAllLabelsViaTraversal() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        g.V(v).dropLabels().iterate();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+
+    @Test
+    public void shouldDropLabelWithConstantTraversal() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        g.V(v).dropLabel(constant("person")).iterate();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("employee"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowWhenDroppingLabelsOnEdgeViaTraversal() {
+        final Vertex v1 = g.addV("person").next();
+        final Vertex v2 = g.addV("person").next();
+        v1.addEdge("knows", v2);
+        g.E().dropLabels().iterate();
+    }
+
+    // --- addV multi-label tests ---
+
+    @Test
+    public void shouldCreateVertexWithMultipleLabelsViaAddV() {
+        final Vertex v = g.addV("a").addLabel("b").next();
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("a", "b"));
+    }
+
+    @Test
+    public void shouldCreateVertexWithDefaultLabelViaAddV() {
+        final Vertex v = g.addV().next();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+
+    // --- hasLabel index consistency tests ---
+
+    @Test
+    public void shouldFindVertexByLabelAfterAddLabel() {
+        final Vertex v = g.addV("person").next();
+        g.V(v).addLabel("employee").iterate();
+        final List<Vertex> found = g.V().hasLabel("employee").toList();
+        assertThat(found, hasSize(1));
+        assertThat(found.get(0).id(), is(v.id()));
+    }
+
+    @Test
+    public void shouldNotFindVertexByLabelAfterDropLabel() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        g.V(v).dropLabel("employee").iterate();
+        final List<Vertex> found = g.V().hasLabel("employee").toList();
+        assertThat(found, hasSize(0));
+    }
+
+    @Test
+    public void shouldFindVertexByBothLabelsWithChainedHasLabel() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final List<Vertex> found = 
g.V().hasLabel("person").hasLabel("employee").toList();
+        assertThat(found, hasSize(1));
+        assertThat(found.get(0).id(), is(v.id()));
+    }
+
+    @Test
+    public void shouldNotFindVertexMissingOneOfChainedHasLabels() {
+        final Vertex v = g.addV("person").next();
+        final List<Vertex> found = 
g.V().hasLabel("person").hasLabel("employee").toList();
+        assertThat(found, hasSize(0));
+    }
+
+    // --- valueMap/elementMap multilabel config tests ---
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void shouldReturnLabelsAsSetWithMultilabelConfig() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final Map<Object, Object> map = g.V(v).valueMap(true)
+                .with(WithOptions.multilabel).next();
+        final Object labelValue = map.get(T.label);
+        assertThat(labelValue, instanceOf(Set.class));
+        final Set<String> labels = (Set<String>) labelValue;
+        assertThat(labels, containsInAnyOrder("person", "employee"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void shouldReturnLabelAsSingleStringWithoutMultilabelConfig() {
+        final Vertex v = g.addV("person").next();
+        final Map<Object, Object> map = g.V(v).valueMap(true).next();
+        final Object labelValue = map.get(T.label);
+        assertThat(labelValue, instanceOf(String.class));
+        assertThat((String) labelValue, is("person"));
+    }
+
+    // --- addLabel pass-through (chaining) test ---
+
+    @Test
+    public void shouldReturnSameVertexAfterAddLabel() {
+        final Vertex v = g.addV("person").next();
+        final Vertex result = g.V(v).addLabel("employee").next();
+        assertThat(result.id(), is(v.id()));
+        assertThat(result.labels(), containsInAnyOrder("person", "employee"));
+    }
+
+    // --- dropLabel pass-through (chaining) test ---
+
+    @Test
+    public void shouldReturnSameVertexAfterDropLabel() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final Vertex result = g.V(v).dropLabel("employee").next();
+        assertThat(result.id(), is(v.id()));
+    }
+
+    // --- elementMap multilabel config test ---
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void shouldReturnLabelsAsSetWithElementMapMultilabelConfig() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        final Map<Object, Object> map = g.V(v).elementMap()
+                .with(WithOptions.multilabel).next();
+        final Object labelValue = map.get(T.label);
+        assertThat(labelValue, instanceOf(Set.class));
+        final Set<String> labels = (Set<String>) labelValue;
+        assertThat(labels, containsInAnyOrder("person", "employee"));
+    }
+
+    // --- GraphTraversalSource multi-label addV test ---
+
+    @Test
+    public void shouldCreateVertexWithMultipleLabelsFromSource() {
+        final Vertex v = g.addV("person", "employee").next();
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("person", "employee"));
+    }
+
+    @Test
+    public void shouldCreateVertexWithThreeLabelsFromSource() {
+        final Vertex v = g.addV("person", "employee", "manager").next();
+        assertThat(v.labels(), hasSize(3));
+        assertThat(v.labels(), containsInAnyOrder("person", "employee", 
"manager"));
+    }
+}
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexMultiLabelGremlinLangTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexMultiLabelGremlinLangTest.java
new file mode 100644
index 0000000000..99d373bcdd
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexMultiLabelGremlinLangTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.structure;
+
+import org.apache.tinkerpop.gremlin.process.remote.EmbeddedRemoteConnection;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static 
org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.containsString;
+
+/**
+ * Tests that verify GremlinLang output for multi-label addV does not 
double-record
+ * addLabel steps, and that the EmbeddedRemoteConnection path works correctly.
+ */
+public class TinkerVertexMultiLabelGremlinLangTest {
+
+    private Graph graph;
+    private GraphTraversalSource g;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        graph.close();
+    }
+
+    @Test
+    public void shouldNotDoubleRecordAddLabelInGremlinLangFromSource() {
+        final GraphTraversal<Vertex, Vertex> t = g.addV("a", "b");
+        final String gremlinLang = t.asAdmin().getGremlinLang().getGremlin();
+        // Should contain addV("a","b") but NOT a trailing addLabel
+        assertThat(gremlinLang, not(containsString("addLabel")));
+    }
+
+    @Test
+    public void shouldNotDoubleRecordAddLabelInGremlinLangFromTraversal() {
+        final GraphTraversal<Vertex, Vertex> t = g.V().addV("a", "b");
+        final String gremlinLang = t.asAdmin().getGremlinLang().getGremlin();
+        // Should contain addV("a","b") but NOT a trailing addLabel
+        assertThat(gremlinLang, not(containsString("addLabel")));
+    }
+
+    @Test
+    public void shouldNotDoubleRecordAddLabelWithThreeLabelsFromSource() {
+        final GraphTraversal<Vertex, Vertex> t = g.addV("a", "b", "c");
+        final String gremlinLang = t.asAdmin().getGremlinLang().getGremlin();
+        assertThat(gremlinLang, not(containsString("addLabel")));
+    }
+
+    @Test
+    public void shouldCreateExactlyOneVertexViaEmbeddedRemoteConnection() 
throws Exception {
+        final GraphTraversalSource remoteG = traversal().with(new 
EmbeddedRemoteConnection(g));
+        try {
+            final List<Vertex> vertices = remoteG.addV("a", "b").toList();
+            assertThat(vertices, hasSize(1));
+            assertThat(vertices.get(0).labels(), hasSize(2));
+            assertThat(vertices.get(0).labels(), containsInAnyOrder("a", "b"));
+        } finally {
+            remoteG.close();
+        }
+    }
+
+    @Test
+    public void shouldCreateExactlyOneVertexWithThreeLabelsViaEmbeddedRemote() 
throws Exception {
+        final GraphTraversalSource remoteG = traversal().with(new 
EmbeddedRemoteConnection(g));
+        try {
+            final List<Vertex> vertices = remoteG.addV("x", "y", "z").toList();
+            assertThat(vertices, hasSize(1));
+            assertThat(vertices.get(0).labels(), hasSize(3));
+            assertThat(vertices.get(0).labels(), containsInAnyOrder("x", "y", 
"z"));
+        } finally {
+            remoteG.close();
+        }
+    }
+
+    @Test
+    public void shouldNotDuplicateVerticesOnRepeatedEmbeddedRemoteCalls() 
throws Exception {
+        final GraphTraversalSource remoteG = traversal().with(new 
EmbeddedRemoteConnection(g));
+        try {
+            remoteG.addV("a", "b").iterate();
+            remoteG.addV("a", "b").iterate();
+            final long count = g.V().count().next();
+            assertThat(count, is(2L));
+        } finally {
+            remoteG.close();
+        }
+    }
+}
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexMultiLabelTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexMultiLabelTest.java
new file mode 100644
index 0000000000..5fc84f8de2
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexMultiLabelTest.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.structure;
+
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for multi-label support on TinkerVertex.
+ */
+public class TinkerVertexMultiLabelTest {
+
+    private Graph graph;
+    private GraphTraversalSource g;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        graph.close();
+    }
+
+    @Test
+    public void shouldCreateVertexWithSingleLabel() {
+        final Vertex v = g.addV("person").next();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("person"));
+    }
+
+    @Test
+    public void shouldCreateVertexWithMultipleLabels() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("person", "employee"));
+    }
+
+    @Test
+    public void shouldCreateVertexWithDefaultLabelWhenNoneSpecified() {
+        final Vertex v = g.addV().next();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void shouldReturnFirstLabelFromDeprecatedLabelMethod() {
+        final Vertex v = g.addV("person").next();
+        assertThat(v.label(), is("person"));
+        assertThat(v.labels().contains(v.label()), is(true));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldReturnUnmodifiableSetFromLabels() {
+        final Vertex v = g.addV("person").next();
+        v.labels().add("hacker");
+    }
+
+    @Test
+    public void shouldAddLabelToExistingVertex() {
+        final Vertex v = g.addV("person").next();
+        v.addLabel("employee");
+        assertThat(v.labels(), hasSize(2));
+        assertThat(v.labels(), containsInAnyOrder("person", "employee"));
+    }
+
+    @Test
+    public void shouldBeIdempotentWhenAddingExistingLabel() {
+        final Vertex v = g.addV("person").next();
+        v.addLabel("person");
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("person"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldThrowWhenAddingNullLabel() {
+        final Vertex v = g.addV("person").next();
+        v.addLabel(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldThrowWhenAddingEmptyLabel() {
+        final Vertex v = g.addV("person").next();
+        v.addLabel("");
+    }
+
+    @Test
+    public void shouldDropAllLabelsAndAssignDefault() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        v.dropLabels();
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+
+    @Test
+    public void shouldDropSpecificLabel() {
+        final Vertex v = g.addV("person").addLabel("employee").next();
+        v.dropLabel("person");
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("employee"));
+    }
+
+    @Test
+    public void shouldBeNoOpWhenDroppingNonExistentLabel() {
+        final Vertex v = g.addV("person").next();
+        final Set<String> before = Set.copyOf(v.labels());
+        v.dropLabel("nonexistent");
+        assertThat(v.labels(), is(before));
+    }
+
+    @Test
+    public void shouldAssignDefaultWhenDroppingLastSpecificLabel() {
+        final Vertex v = g.addV("person").next();
+        v.dropLabel("person");
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowWhenAddingLabelToEdge() {
+        final Vertex v1 = g.addV("person").next();
+        final Vertex v2 = g.addV("person").next();
+        final Edge e = v1.addEdge("knows", v2);
+        e.addLabel("friend");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowWhenDroppingLabelsOnEdge() {
+        final Vertex v1 = g.addV("person").next();
+        final Vertex v2 = g.addV("person").next();
+        final Edge e = v1.addEdge("knows", v2);
+        e.dropLabels();
+    }
+
+    @Test
+    public void shouldReturnSingletonSetForEdgeLabels() {
+        final Vertex v1 = g.addV("person").next();
+        final Vertex v2 = g.addV("person").next();
+        final Edge e = v1.addEdge("knows", v2);
+        assertThat(e.labels(), hasSize(1));
+        assertThat(e.labels(), containsInAnyOrder("knows"));
+    }
+
+    @Test
+    public void shouldRemoveDefaultLabelWhenAddingFirstRealLabel() {
+        final Vertex v = g.addV().next();
+        assertThat(v.labels(), containsInAnyOrder(Vertex.DEFAULT_LABEL));
+        v.addLabel("person");
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("person"));
+    }
+
+    @Test
+    public void shouldDeduplicateLabelsOnAddV() {
+        final Vertex v = g.addV("person").addLabel("person").next();
+        // addLabel("person") on a vertex already labeled "person" is 
idempotent
+        assertThat(v.labels(), hasSize(1));
+        assertThat(v.labels(), containsInAnyOrder("person"));
+    }
+}


Reply via email to