Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
da4ec2a
Add multi-label vertex support: core data model, steps, and grammar
xiazcy Jun 26, 2026
be3e714
Implement multi-label in TinkerGraph reference implementation
xiazcy Jun 26, 2026
f4180a2
Add GLV implementations for multi-label across Go, Python, JS, .NET
xiazcy Jun 26, 2026
be5072e
Document multi-label vertex support
xiazcy Jun 26, 2026
6a3c91e
Reconstruct source-spawned multi-label addV as a single step
Cole-Greer Jun 30, 2026
99f496a
Preserve GValue variables through multi-label addV and simplify the a…
Cole-Greer Jun 30, 2026
687878f
Add label-traversal semantics, addV traversal-varargs, and multi-labe…
Cole-Greer Jul 2, 2026
dfc1827
Address multi-label documentation review comments
Cole-Greer Jul 2, 2026
561723f
Fold LabelsDropVerificationStrategy into StandardVerificationStrategy
Cole-Greer Jul 2, 2026
d6c57e2
cleanup gherkin tags docs
Cole-Greer Jul 2, 2026
1310986
Fix gremlin-core compilation for addLabel(Traversal) type inference
Cole-Greer Jul 2, 2026
f1e678b
Test multi-label single-label-default rendering via one-of assertion
Cole-Greer Jul 2, 2026
102938e
Align addLabel()/dropLabel() traversal-varargs with addV() semantics
Cole-Greer Jul 2, 2026
b88f22e
Simplify addV/addLabel/dropLabel grammar visitors
Cole-Greer Jul 3, 2026
43e0336
Fix multi-label addV codegen in GremlinDslProcessor
Cole-Greer Jul 3, 2026
9ea3df1
Use literal-only grammar for addLabel()/dropLabel() strings
Cole-Greer Jul 3, 2026
2958a30
Unify multi-label representation in addV step hierarchy
Cole-Greer Jul 5, 2026
278b64b
Fix multi-label rendering gaps in ElementMapStep and ComputerGraph
Cole-Greer Jul 6, 2026
d272791
Address PR review nits: verification strategy, event util, label help…
Cole-Greer Jul 6, 2026
0b33a8f
Fix @MultiLabelDefault/@SingleLabelDefault semantics and add coverage
Cole-Greer Jul 6, 2026
2eefc22
Address multi-label test coverage gap review comments
Cole-Greer Jul 6, 2026
083c9a6
Split TinkerGraph empty-graph fixture into single/multi-label variants
Cole-Greer Jul 7, 2026
9db5db5
Reject combined with(multilabel)/with(singlelabel), add elementMap() …
Cole-Greer Jul 7, 2026
7855ccf
Relocate hasLabel() OR-semantics note, fix 'supports mutation' wording
Cole-Greer Jul 7, 2026
db79ef9
Weave multi-label support throughout reference docs, recipes, tutorial
Cole-Greer Jul 7, 2026
d036429
Rewrite spec-style multi-label prose in reference docs; drop redundan…
Cole-Greer Jul 8, 2026
941dd3e
fix mergeV example not using 'existing' graph
Cole-Greer Jul 8, 2026
3e761ce
Support multi-label vertices in StarGraph GraphSON V4 serialization
Cole-Greer Jul 9, 2026
e5f35a4
Support multi-label vertices in Gryo; fix HadoopElement.labels()
Cole-Greer Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Changed `Tree` to no longer extend `HashMap`; it is now a final class with a tree-shaped API (`childAt`, `hasChild`, `contains`, `findSubtree`, `getOrCreateChild`, `getNodesAtDepth`, `getLeafNodes`, `nodeCount`) and is no longer a `Map`.
* Changed `count(local)` on a `Tree` to return the total node count (`Tree.nodeCount()`) instead of the root-entry count.
* Changed Tree class in Java to not extend from HashMap and offered a new tree-shaped API for navigation.
* Added multi-label support for vertices with configurable `LabelCardinality` (`ONE`, `ONE_OR_MORE`, `ZERO_OR_MORE`).
* Added `labels()` step to emit each vertex label as a separate traverser.
* Added `addLabel()` and `dropLabel()`/`dropLabels()` steps for vertex label mutation.
* Added `LabelCardinalityValidator` utility for constraint enforcement separated from the `LabelCardinality` enum.
* Added `with("multilabel")` source configuration for `elementMap()` and `valueMap()` to return labels as a set.
* Added `labels().drop()` rejection to `StandardVerificationStrategy` to prevent accidental usage.
* Added multi-label syntax to `addV()` and `mergeV()` with append-only `onMatch` semantics.
* Added typed numeric wrappers and `preciseNumbers` connection option to `gremlin-javascript` for explicit control over numeric type serialization and deserialization.
* Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result iteration, providing API parity with `next(n)` in the Java, Python, and .NET GLVs, and updated the Go translators in `gremlin-core` and `gremlin-javascript` to emit `NextN(n)` for the batched form.
* Added Provider Defined Types (PDT) support — graph providers can define custom types via `@ProviderDefined` annotation that serialize/deserialize seamlessly across all GLVs without driver-side configuration. Replaces TP3 custom type mechanism.
Expand Down
3 changes: 3 additions & 0 deletions docker/gremlin-server/gremlin-server-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ graphs: {
graph: {
configuration: conf/tinkergraph-service.properties,
traversalSources: [{name: g}, {name: ggraph}]},
multilabel: {
configuration: conf/tinkergraph-multilabel.properties,
traversalSources: [{name: gmultilabel}]},
immutable: {
configuration: conf/tinkergraph-service.properties,
traversalSources: [{name: gimmutable}]},
Expand Down
9 changes: 9 additions & 0 deletions docs/src/dev/developer/for-committers.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -672,8 +672,17 @@ TinkerGQL support out of the box and runs these scenarios without any additional
* `@InsertionOrderingRequired` - The scenario is reliant on the graph system predictably returning results (vertices,
edges, properties) in the same order in which they were inserted into the graph.
* `@MetaProperties` - The scenario makes use of meta-properties.
* `@MultiLabel` - The scenario requires a graph that supports multiple labels per vertex (i.e.
`ONE_OR_MORE` or `ZERO_OR_MORE` vertex label cardinality). Providers that only support single-label vertices
should exclude these tests.
* `@MultiLabelDefault` - The scenario assumes a graphs unconfigured `valueMap()`/`elementMap()` steps
produce results following `with("multilabel")` semantics. Graphs which default to `with("singlelabel")` semantics
should exclude these tests.
* `@MultiProperties` - The scenario makes use of multi-properties.
* `@SupportsChar` - The scenario requires that `char` is completely supported.
* `@SingleLabelDefault` - The scenario assumes a graphs unconfigured `valueMap()`/`elementMap()` steps
produce results following `with("singlelabel")` semantics. Graphs which default to `with("multilabel")` semantics
should exclude these tests.
* `@SupportsDuration` - The scenario requires that `Duration` is completely supported.
* `@SupportsBinary` - The scenario requires that `Binary` is completely supported.
* `@UserSuppliedVertexIds` - The scenario relies on the vertex IDs specified in the dataset used by the scenario.
Expand Down
131 changes: 131 additions & 0 deletions docs/src/dev/provider/gremlin-semantics.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,84 @@ See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/j
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java[source (start)],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#addvertex-step[reference]

=== addLabel()
Comment thread
Cole-Greer marked this conversation as resolved.

*Description:* Adds one or more labels to an element.

*Syntax:* `addLabel(String label, String... moreLabels)` | `addLabel(Traversal<?, ?> labelTraversal, Traversal<?, ?>... moreLabelTraversals)`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`Element`
|=========================================================

*Arguments:*

* `label` - The label to add.
* `moreLabels` - Additional labels to add.
* `labelTraversal` - The first (or only) traversal that produces a label to add.
* `moreLabelTraversals` - Additional label-producing traversals (may be empty for single-traversal behavior).

*Considerations:*

@spmallette spmallette Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The graph must be configured with a LabelCardinality that supports mutation (ONE_OR_MORE or ZERO_OR_MORE).

"supports mutation"? First, that sounds a bit odd...LabelCardinality is not really "mutation" right? Does single imply immutability? is that right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reworded this for clarity, but yes singlelabel does imply immutability, because there is no atomic operation to replace labels, and there labels cannot be added or removed without violating the cardinality. This also keeps LabelCardinality.ONE consistent with the legacy TP3 semantics which I think is nice.


Label add and remove operations require a `LabelCardinality` that permits changing the set of labels on an element
(`ONE_OR_MORE` or `ZERO_OR_MORE`). When `LabelCardinality` is `ONE`, the vertex's label is fixed at creation and
cannot be added to or removed.
Adding a label that already exists on the element is a no-op. The step returns the element (sideEffect semantics).
Each traversal argument is iterated for a single result only. If exactly one traversal is provided and it produces
a `Collection<String>`, the collection will be automatically unfolded into the set of labels added; if more than one
traversal is provided, each must resolve to a single `String` label.

*Exceptions*

* If the graph's `LabelCardinality` does not permit label modification, an `IllegalStateException` will be thrown.
* If any label argument is null or empty, an `IllegalArgumentException` will be thrown.
* If more than one traversal is provided and any of them produces a `Collection`, an `IllegalArgumentException` will
be thrown.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddLabelStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#addlabel-step[reference]

=== dropLabel()

*Description:* Removes one or more specific labels from an element, or removes all labels.

*Syntax:* `dropLabel(String label, String... moreLabels)` | `dropLabel(Traversal<?, String> labelTraversal, Traversal<?, String>... moreLabelTraversals)` | `dropLabels()`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`Element`
|=========================================================

*Arguments:*

* `label` - The label to remove.
* `moreLabels` - Additional labels to remove.
* `labelTraversal` - The first (or only) traversal that produces a label to remove.
* `moreLabelTraversals` - Additional label-producing traversals (may be empty for single-traversal behavior).

*Considerations:*

Label add and remove operations require a `LabelCardinality` that permits changing the set of labels on an element
(`ONE_OR_MORE` or `ZERO_OR_MORE`). Dropping a label that does not exist
on the element is a no-op. `dropLabels()` removes all labels (only valid with `ZERO_OR_MORE`). The step returns the
element (sideEffect semantics).
Each traversal argument is iterated for a single result only. If exactly one traversal is provided and it produces
a `Collection<String>`, the collection will be automatically unfolded into the set of labels removed; if more than
one traversal is provided, each must resolve to a single `String` label.

*Exceptions*

* If the graph's `LabelCardinality` does not permit label modification, an `IllegalStateException` will be thrown.
* If `dropLabel()` or `dropLabels()` would violate the minimum label count, an `IllegalStateException` will be thrown.
* If more than one traversal is provided and any of them produces a `Collection`, an `IllegalArgumentException` will
be thrown.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/DropLabelsStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#droplabel-step[reference]

[[all-step]]
=== all()

Expand Down Expand Up @@ -1637,6 +1715,52 @@ See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/j
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountSideEffectStep.java[source (sideEffect)],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#groupcount-step[reference]

[[label-step]]
=== label()

*Description:* Maps an element to its label as a single string value.

*Syntax:* `label()`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`String`
|=========================================================

*Considerations:*

The `label()` step returns exactly one label string per element. For graphs that support multiple labels per vertex,
`label()` returns only one of the vertex's labels and the choice is non-deterministic (implementation-dependent
ordering). Use `labels()` to retrieve all labels reliably.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#label-step[reference]

[[labels-step]]
=== labels()
Comment thread
Cole-Greer marked this conversation as resolved.

*Description:* Maps an element to its labels, emitting one traverser per label.

*Syntax:* `labels()`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`String`
|=========================================================

*Considerations:*

For vertices with a single label, `labels()` emits one traverser (equivalent to `label()`). For multi-label vertices,
it emits one traverser per label. For vertices with zero labels (`ZERO_OR_MORE` mode), no traversers are emitted.

`hasLabel("a", "b")` uses OR semantics: it matches any vertex that has label "a" or label "b". To match vertices
Comment thread
Cole-Greer marked this conversation as resolved.
Outdated
with all specified labels, chain calls: `hasLabel("a").hasLabel("b")`.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelsStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#labels-step[reference]

[[length-step]]
=== length()

Expand Down Expand Up @@ -2564,6 +2688,13 @@ the possible values are:
** 8 for including values (VertexProperty)
** 15 for including all

The label format in the map is controlled by source-level `with("multilabel")` and `with("singlelabel")` options, not
by the graph's `LabelCardinality`. When `"multilabel"` is present and `"singlelabel"` is not, the label value is a

@spmallette spmallette Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When "multilabel" is present and "singlelabel" is not,

that's an odd way to say that, no? it's not like they make any sense both being configured. ah, but then:

When both options are present on the same source, "singlelabel" always takes precedence regardless of the order in which they were applied.

I'm not sure I like that. I suppose it's better than an error? Was this an explicit design choice?

`Set<String>`; otherwise it is a single `String`. As a deliberate design choice, when both options are present on the
same source, `"singlelabel"` always takes precedence regardless of the order in which they were applied. This ensures
deterministic behavior rather than relying on declaration order. Providers who want label output tied to their
cardinality configuration should override `PropertyMapStep` and/or `ElementMapStep`.
Comment thread
Cole-Greer marked this conversation as resolved.
Outdated

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#valuemap-step[reference],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#propertymap-step[reference]
30 changes: 30 additions & 0 deletions docs/src/reference/implementations-tinkergraph.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ TinkerGraph has several settings that can be provided on creation via `Configura
|gremlin.tinkergraph.vertexPropertyIdManager |The `IdManager` implementation to use for vertex properties.
|gremlin.tinkergraph.defaultVertexPropertyCardinality |The default `VertexProperty.Cardinality` to use when `Vertex.property(k,v)` is called.
|gremlin.tinkergraph.allowNullPropertyValues |A boolean value that determines whether or not `null` property values are allowed and defaults to `false`.
|gremlin.tinkergraph.vertexLabelCardinality |The `LabelCardinality` for vertices. Options are `ONE` (default, single immutable label, backward-compatible with 3.x), `ONE_OR_MORE` (at least one label, mutable), or `ZERO_OR_MORE` (zero or more labels). See <<tinkergraph-multi-label>>.
|gremlin.tinkergraph.graphLocation |The path and file name for where TinkerGraph should persist the graph data. If a
value is specified here, the `gremlin.tinkergraph.graphFormat` should also be specified. If this value is not
included (default), then the graph will stay in-memory and not be loaded/persisted to disk.
Expand Down Expand Up @@ -219,6 +220,35 @@ g.io("data/tinkerpop-crew.kryo").read().iterate()
g.V().properties()
----

[[tinkergraph-multi-label]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're missing a lot of docs updates. TinkerGraph isn't the story here. The story is that TinkerPop now has multi-label support. The entire reference guide, recipes, tutorials, etc. need review. At minimum, we have to conceptually introduce this change to the "structure" at the front end of the reference docs: https://tinkerpop.apache.org/docs/current/reference/#graph-structure We should be looking to explain this feature throughout the docs in a seamless fashion.

=== Multi-Label Support

TinkerGraph supports multiple labels per vertex when `gremlin.tinkergraph.vertexLabelCardinality` is set to
`ONE_OR_MORE` or `ZERO_OR_MORE`. This enables use of the `addLabel()`, `dropLabel()`, `dropLabels()`, and `labels()`
traversal steps.

[gremlin-groovy]
----
conf = new BaseConfiguration()
conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE")
graph = TinkerGraph.open(conf)
g = traversal().with(graph)
g.addV("person","employee").property("name","marko").iterate()
g.V().has("name","marko").labels() <1>
g.V().has("name","marko").addLabel("manager").labels() <2>
g.V().has("name","marko").dropLabel("employee").labels() <3>
----

<1> The vertex was created with two labels.
<2> A third label is added.
<3> One label is removed.

The `hasLabel()` step works with multi-label vertices using OR semantics: `hasLabel("a","b")` matches any vertex that
has label "a" OR label "b". To match vertices having both labels, chain multiple `hasLabel()` calls:
`hasLabel("a").hasLabel("b")`.

IMPORTANT: Edges always have exactly one label — multi-label support applies to vertices only.

[[tinkergraph-gremlin-tx]]
=== Transactions

Expand Down
11 changes: 11 additions & 0 deletions docs/src/reference/the-graph.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ g.V().has('name','gremlin').inE('uses').
select('a','b').by('skill').by('name') // rank the users of gremlin by their skill level
----

[[vertex-labels]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that since we make this introduction here, it would be good to describe use cases. This just tells the reader they can do it maybe but doesn't explain much about what it would be used for and when you should and shouldn't reach for it..

=== Vertex Labels

In addition to multi-properties and meta-properties, TinkerPop supports multiple labels on a single vertex. The number
of labels a vertex may carry is governed by the graph's configured `LabelCardinality`: `ONE` fixes exactly one immutable
label per vertex (the traditional model), `ONE_OR_MORE` requires at least one label but permits additional labels to be
added or removed, and `ZERO_OR_MORE` allows a vertex to have any number of labels including none. See the
link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_label_cardinality[provider documentation] for configuration
details and the <<label-step,label()>>, <<labels-step,labels()>>, <<addlabel-step,addLabel()>>, and
<<droplabel-step,dropLabel()>> steps for traversal access.

== Graph Variables

`Graph.Variables` are key/value pairs associated with the graph itself -- in essence, a `Map<String,Object>`. These
Expand Down
Loading
Loading