When working with graph databases powered by Apache TinkerPop, Gremlin developers have long relied on valueMap() to extract vertex and edge properties. While powerful, valueMap() often requires additional steps to retrieve element metadata such as id, label, or edge endpoint details, especially when working with edges.
With the release of Apache TinkerPop 3.4.4, Gremlin introduced elementMap(), a step designed to simplify result projection and reduce query verbosity.
At first glance, elementMap() appears similar to valueMap(). However, its real strength becomes evident when:
· You want id and label included automatically
· You are working with edges and need IN and OUT vertex metadata
· You want cleaner traversals with fewer project() and by() steps
· You prefer property values returned directly instead of wrapped in lists
This post explores:
· How elementMap() differs from valueMap()
· Where it simplifies vertex queries
· Why it significantly improves edge result readability
· When you should still prefer valueMap() (e.g., multi-cardinality properties)
Through practical Gremlin examples, we’ll compare both approaches side by side and show how elementMap() reduces boilerplate while improving clarity especially in edge-heavy traversals.
If you regularly work with graph structures, lineage models, or traversal-heavy analytics, understanding this distinction can lead to noticeably cleaner and more maintainable Gremlin queries.
1. E-Commerce Domain Model Overview
To demonstrate the different ways valueMap() can be controlled using WithOptions, we will use a simple e-commerce domain model built using an in-memory TinkerGraph. This model is intentionally small but expressive enough to showcase real-world traversal patterns.
At a high level, the graph represents:
· Customers placing orders
· Orders containing products
· Products belonging to categories
· Orders being delivered to cities
This gives us multiple vertex labels, relationships, and property types to work with and ideal for exploring how valueMap() behaves across different scenarios.
Vertex Types
· City: Represents delivery locations
· Category: Represents product categories
· Product: Represents items that can be purchased
· Customer: Represents users of the platform
· Order: Represents purchase transactions
Each vertex label has a small and focused set of properties, which makes it easier to see how valueMap() returns property data and how WithOptions affects the output.
Edge Types and Relationships
· belongsTo: Connects a product to its category
· placed: Connects a customer to an order
· contains: Connects an order to one or more product vertices. An order may contain multiple products
· deliveredTo: Connects an order to a city, represents the delivery destination for the order
Gremlin Code to build the Graph
graph = TinkerGraph.open() g = graph.traversal() blr = g.addV('city').property('name', 'Bengaluru').next() hyd = g.addV('city').property('name', 'Hyderabad').next() chn = g.addV('city').property('name', 'Chennai').next() pne = g.addV('city').property('name', 'Pune').next() catElectronics = g.addV('category').property('name', 'Electronics').next() catBooks = g.addV('category').property('name', 'Books').next() catClothing = g.addV('category').property('name', 'Clothing').next() p1 = g.addV('product').property('sku', 'P1001').property('name', 'Laptop').next() p2 = g.addV('product').property('sku', 'P1002').property('name', 'Smartphone').next() p3 = g.addV('product').property('sku', 'P2001').property('name', 'Java Programming Book').next() p4 = g.addV('product').property('sku', 'P3001').property('name', 'T-Shirt').next() g.V(p1).addE('belongsTo').to(catElectronics).next() g.V(p2).addE('belongsTo').to(catElectronics).next() g.V(p3).addE('belongsTo').to(catBooks).next() g.V(p4).addE('belongsTo').to(catClothing).next() c1 = g.addV('customer').property('customerId', 'C101').property('name', 'Amit Sharma').next() c2 = g.addV('customer').property('customerId', 'C102').property('name', 'Priya Iyer').next() c3 = g.addV('customer').property('customerId', 'C103').property('name', 'Rohit Verma').next() o1 = g.addV('order').property('orderId', 'O9001').next() o2 = g.addV('order').property('orderId', 'O9002').next() o3 = g.addV('order').property('orderId', 'O9003').next() o4 = g.addV('order').property('orderId', 'O9004').next() o5 = g.addV('order').property('orderId', 'O9005').next() g.V(c1).addE('placed').to(o1).next() g.V(c1).addE('placed').to(o2).next() g.V(c2).addE('placed').to(o3).next() g.V(c3).addE('placed').to(o4).next() g.V(c3).addE('placed').to(o5).next() g.V(o1).addE('contains').to(p1).next() g.V(o1).addE('contains').to(p3).next() g.V(o2).addE('contains').to(p2).next() g.V(o3).addE('contains').to(p4).next() g.V(o4).addE('contains').to(p1).next() g.V(o4).addE('contains').to(p4).next() g.V(o5).addE('contains').to(p3).next() g.V(o1).addE('deliveredTo').to(blr).next() g.V(o2).addE('deliveredTo').to(blr).next() g.V(o3).addE('deliveredTo').to(hyd).next() g.V(o4).addE('deliveredTo').to(chn).next() g.V(o5).addE('deliveredTo').to(pne).next()
gremlin> g.V().valueMap(true) ==>[id:0,label:city,name:[Bengaluru]] ==>[id:33,label:customer,customerId:[C102],name:[Priya Iyer]] ==>[id:2,label:city,name:[Hyderabad]] ==>[id:4,label:city,name:[Chennai]] ==>[id:36,label:customer,customerId:[C103],name:[Rohit Verma]] ==>[id:6,label:city,name:[Pune]] ==>[id:39,label:order,orderId:[O9001]] ==>[id:8,label:category,name:[Electronics]] ==>[id:41,label:order,orderId:[O9002]] ==>[id:10,label:category,name:[Books]] ==>[id:43,label:order,orderId:[O9003]] ==>[id:12,label:category,name:[Clothing]] ==>[id:45,label:order,orderId:[O9004]] ==>[id:14,label:product,name:[Laptop],sku:[P1001]] ==>[id:47,label:order,orderId:[O9005]] ==>[id:17,label:product,name:[Smartphone],sku:[P1002]] ==>[id:20,label:product,name:[Java Programming Book],sku:[P2001]] ==>[id:23,label:product,name:[T-Shirt],sku:[P3001]] ==>[id:30,label:customer,customerId:[C101],name:[Amit Sharma]] gremlin> gremlin> gremlin> g.E().valueMap(true) ==>[id:64,label:deliveredTo] ==>[id:65,label:deliveredTo] ==>[id:49,label:placed] ==>[id:50,label:placed] ==>[id:51,label:placed] ==>[id:52,label:placed] ==>[id:53,label:placed] ==>[id:54,label:contains] ==>[id:55,label:contains] ==>[id:56,label:contains] ==>[id:57,label:contains] ==>[id:26,label:belongsTo] ==>[id:58,label:contains] ==>[id:27,label:belongsTo] ==>[id:59,label:contains] ==>[id:28,label:belongsTo] ==>[id:60,label:contains] ==>[id:29,label:belongsTo] ==>[id:61,label:deliveredTo] ==>[id:62,label:deliveredTo] ==>[id:63,label:deliveredTo]
2. What is elementMap()?
elementMap() is a Gremlin step that returns a map representation of a vertex or edge including:
· id
· label
· element properties
· (for edges) metadata about connected vertices
It is designed to provide a clean, structured view of graph elements, especially useful for debugging, API responses, and traversal inspection.
Using elementMap() on Vertices
g.V(). hasLabel('product'). elementMap()
gremlin> g.V(). ......1> hasLabel('product'). ......2> elementMap() ==>[id:14,label:product,name:Laptop,sku:P1001] ==>[id:17,label:product,name:Smartphone,sku:P1002] ==>[id:20,label:product,name:Java Programming Book,sku:P2001] ==>[id:23,label:product,name:T-Shirt,sku:P3001]
What you get automatically
· The internal vertex ID
· The vertex label (product)
· All properties (sku, name)
Selecting Specific Properties
You can request only certain properties.
g.V(). hasLabel('product'). elementMap('name')
gremlin> g.V(). ......1> hasLabel('product'). ......2> elementMap('name') ==>[id:14,label:product,name:Laptop] ==>[id:17,label:product,name:Smartphone] ==>[id:20,label:product,name:Java Programming Book] ==>[id:23,label:product,name:T-Shirt]
Even when selecting specific properties id and label are still included.
Using elementMap() on edges
Edges are where elementMap() becomes really powerful.
g.E(). hasLabel('deliveredTo'). elementMap()
gremlin> g.E(). ......1> hasLabel('deliveredTo'). ......2> elementMap() ==>[id:64,label:deliveredTo,IN:[id:4,label:city],OUT:[id:45,label:order]] ==>[id:65,label:deliveredTo,IN:[id:6,label:city],OUT:[id:47,label:order]] ==>[id:61,label:deliveredTo,IN:[id:0,label:city],OUT:[id:39,label:order]] ==>[id:62,label:deliveredTo,IN:[id:0,label:city],OUT:[id:41,label:order]] ==>[id:63,label:deliveredTo,IN:[id:2,label:city],OUT:[id:43,label:order]]
As you observe the output, for each edge
· Edge ID is included
· Edge label is included
· IN vertex metadata is included
· OUT vertex metadata is included
This is extremely valuable in business domains like your e-commerce graph because it shows:
· What is the order
· Where the order delivered to
· Who placed the order
· The direction of the relationship
Traversing and Returning Structured Results
Show all products ordered by Amit Sharma
g.V(). has('customer','name','Amit Sharma'). out('placed'). out('contains'). elementMap()
gremlin> g.V(). ......1> has('customer','name','Amit Sharma'). ......2> out('placed'). ......3> out('contains'). ......4> elementMap() ==>[id:14,label:product,name:Laptop,sku:P1001] ==>[id:20,label:product,name:Java Programming Book,sku:P2001] ==>[id:17,label:product,name:Smartphone,sku:P1002]
Inspecting Delivery Flow
Where was order O9001 delivered?
g.V(). hasLabel('order'). has('orderId', 'O9001'). outE('deliveredTo'). elementMap()
gremlin> g.V(). ......1> hasLabel('order'). ......2> has('orderId', 'O9001'). ......3> outE('deliveredTo'). ......4> elementMap() ==>[id:61,label:deliveredTo,IN:[id:0,label:city],OUT:[id:39,label:order]]
From the above output, we can get the order and delivered city details. Here the order id is 39L (ids are long), lets get order details.
g.V(39L). out('contains'). elementMap()
gremlin> g.V(39L). ......1> out('contains'). ......2> elementMap() ==>[id:14,label:product,name:Laptop,sku:P1001] ==>[id:20,label:product,name:Java Programming Book,sku:P2001]
From the output, we can confirm that the order contains Laptop and Java Programming Book.
Why elementMap() Is Ideal in Business Graphs?
In your e-commerce domain "Customers → Orders → Products → Categories → Cities", when traversals get longer, understanding context becomes harder.
elementMap() solves that by:
· Returning structured maps
· Including labels automatically
· Showing direction on edges
· Eliminating the need for verbose projections
3. How elementMap() differs from valueMap()?
Although both steps return map-like results, they are fundamentally different in behavior and intent.
3.1 ID and Label Handling
By default, valueMap do not return id, label, it returns only properties.
gremlin> g.V().hasLabel('product').valueMap() ==>[name:[Laptop],sku:[P1001]] ==>[name:[Smartphone],sku:[P1002]] ==>[name:[Java Programming Book],sku:[P2001]] ==>[name:[T-Shirt],sku:[P3001]]
elementMap() return id, label along with other properties.
gremlin> g.V().hasLabel('product').elementMap() ==>[id:14,label:product,name:Laptop,sku:P1001] ==>[id:17,label:product,name:Smartphone,sku:P1002] ==>[id:20,label:product,name:Java Programming Book,sku:P2001] ==>[id:23,label:product,name:T-Shirt,sku:P3001]
3.2 Property Value Structure
valueMap() wraps values in lists, where as elementMap() returns scalar value.
g.V(14L).valueMap()
gremlin> g.V(14L).valueMap() ==>[name:[Laptop],sku:[P1001]]
g.V(14L).elementMap()
gremlin> g.V(14L).elementMap() ==>[id:14,label:product,name:Laptop,sku:P1001]
As you observe above outputs,
· valueMap() return property values as lists name:[Laptop],sku:[P1001], where as
· elementMap() return property values as scalar.
What if the property value contain list of values?
In this case, elementMap() returns only one value from the list, where as valueMap() return all the values associated with a property.
Let's add a tags property with two values.
g.V(14L).property(list,'tags','electronics') g.V(14L).property(list,'tags','premium')
gremlin> g.V(14L).valueMap() ==>[name:[Laptop],sku:[P1001],tags:[electronics,premium]] gremlin> gremlin> g.V(14L).elementMap() ==>[id:14,label:product,name:Laptop,sku:P1001,tags:premium]
3.3 Edge Behavior (Biggest Difference)
Let’s inspect a deliveredTo edge.
g.V(). has('order','orderId','O9001'). outE('deliveredTo'). elementMap()
gremlin> g.V(). ......1> has('order','orderId','O9001'). ......2> outE('deliveredTo'). ......3> elementMap() ==>[id:61,label:deliveredTo,IN:[id:0,label:city],OUT:[id:39,label:order]]
Here the output contains
· Edge ID
· Edge label
· IN vertex metadata
· OUT vertex metadata
Where as valueMap do not include IN and OUT vertex details.
gremlin> g.V(). ......1> has('order','orderId','O9001'). ......2> outE('deliveredTo'). ......3> valueMap(true) ==>[id:61,label:deliveredTo]
As Gremlin traversals grow more complex, the way we inspect and project graph elements becomes increasingly important. Through our e-commerce domain example built on Apache TinkerPop using TinkerGraph, we’ve seen that elementMap() is not just another projection step, it changes how we interact with graph data.
It provides:
· Automatic inclusion of id and label
· Clean, scalar property values
· Built-in edge direction metadata (IN and OUT)
· Significantly reduced traversal verbosity
· More readable debugging output
For edge-heavy traversals especially, elementMap() removes the need for repetitive project() patterns and manual vertex extraction. The result is Gremlin code that is easier to read, easier to maintain, and easier to reason about.
At the same time, understanding its behavior around multi-cardinality properties is essential. Like most Gremlin steps, it is powerful when used intentionally.
Previous Next Home
No comments:
Post a Comment