---
title: "Multi-properties"
description: "Guide to modeling and querying vertex multi-properties with list and set cardinality in Aerospike Graph Service."
---

# Multi-properties

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

Aerospike Graph Service (AGS) supports Apache TinkerPop’s model for vertex [multi-properties](https://tinkerpop.apache.org/docs/current/reference/#vertex-properties) (a vertex property with multiple values).

This page explains how to define, query, and update multi-properties using `Cardinality.list` and `Cardinality.set`.

::: note
_Cardinality_ has two distinct meanings:  
  
On this page, _cardinality_ refers to vertex property cardinality (`single`, `list`, or `set`). Vertex property cardinality is the number of values a single property key can have on a single vertex. Each value is a separate [VertexProperty](https://tinkerpop.apache.org/docs/current/reference/#vertex-properties) object and can itself hold properties (meta-properties). This aligns with TinkerPop’s [definition](https://tinkerpop.apache.org/javadocs/3.2.1/full/org/apache/tinkerpop/gremlin/structure/VertexProperty.Cardinality.html).  
  
Aerospike’s [index cardinality](https://aerospike.com/docs/graph/develop/query/indexing/#cardinality) refers to how many distinct values exist for a property key across the entire dataset. Index cardinality is used for query optimization and is unrelated to the vertex property cardinality.
:::

## Cardinality options

-   `list`: Stores multiple values per property key. New values are appended without checking for duplicates.
-   `set`: Stores multiple unique values per property key. Duplicate key-value pairs are not added.

## Defining multi-properties

To associate multiple values with a property key on a vertex, use `VertexProperty.Cardinality.list` or `VertexProperty.Cardinality.set`.

### Using list cardinality

With `list` cardinality, each call to `.property()` appends a new value without checking for duplicates:

```groovy
g.addV("person")

 .property(VertexProperty.Cardinality.list, "alias", "Sam")

 .property(VertexProperty.Cardinality.list, "alias", "Sammy")

 .iterate();
```

This results in a vertex with two `alias` values: `Sam` and `Sammy`. Each property value is stored independently under the same key.

### Using set cardinality

With `set` cardinality, duplicate key-value pairs are not added:

```groovy
final Object vertexId = g.addV().id().next();

g.V(vertexId).property("foo", 1).next();

g.V(vertexId).properties("foo").count().next(); // count == 1

g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1L).next();

g.V(vertexId).properties("foo").count().next(); // count == 1 (not added, same value)

g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1.0).next();

g.V(vertexId).properties("foo").count().next(); // count == 2 (added, different type)
```

When you add a vertex property with `set` cardinality:

-   If a property with the same key and value already exists, the existing property is returned without adding a duplicate.
-   If the key-value combination is unique, a new vertex property is added.

For details on type comparison and selection behavior, see [Set cardinality behavior](#set-cardinality-behavior).

## Querying multi-properties

The `.has()` step automatically matches any value stored under a multi-property:

```groovy
g.V().has("alias", "Sam").toList();                       // Matches

g.V().has("alias", "Sammy").toList();                     // Matches

g.V().has("alias", "Sam").has("alias", "Sammy").toList(); // Still matches
```

These semantics allow for concise multi-value filtering.

## Updating vertex property cardinality

By default, property cardinality is `single`, meaning any new value overwrites the previous one:

```groovy
g.addV("person").property("name", "Alice").iterate();

g.V().has("name", "Alice").property("name", "Ally").iterate();

// Result: "name" is now "Ally"
```

To preserve multiple values, you must explicitly use `Cardinality.list` or `Cardinality.set`:

```groovy
g.addV("person")

 .property(VertexProperty.Cardinality.list, "alias", "Alice")

 .property(VertexProperty.Cardinality.list, "alias", "Ally")

 .iterate();

// Result: both "Alice" and "Ally" stored under "alias"
```

Each invocation of `.property()` adds a new value without replacing the existing ones.

## Meta-properties

Each vertex property value is stored as a separate object and can include its own meta-properties. These meta-properties are attached to individual property values, not to the vertex itself.

### Defining and querying meta-properties

```groovy
g.addV("person")

 // livedIn = "San Francisco", with meta-properties: year = 2023 and season = Spring

 .property(VertexProperty.Cardinality.list, "livedIn", "San Francisco", "year", "2023", "season", "Spring")

 // livedIn = "Seattle", with meta-properties: year = 2024 and season = Summer

 .property(VertexProperty.Cardinality.list, "livedIn", "Seattle", "year", "2024", "season", "Summer")

 // livedIn = "San Francisco", with meta-properties: year = 2025 and season = Winter

 .property(VertexProperty.Cardinality.list, "livedIn", "San Francisco", "year", "2025", "season", "Winter")

 .iterate();
```

Meta-properties like `year` or `season` can be queried using `.properties()`:

```groovy
g.V().properties("livedIn").has("year", "2024").value().toList();   // Returns "Seattle"

g.V().properties("livedIn").has("season", "Winter").value().toList(); // Returns "San Francisco"
```

This pattern is useful when you need to differentiate between multiple values of the same property using additional contextual data.

### Meta-properties with set cardinality

When you add a vertex property with meta-properties using `set` cardinality and a matching property already exists:

-   If the meta-property key does not exist on the existing property, the meta-property is added.
-   If the meta-property key already exists, its value is updated.
-   Meta-property updates apply only to the single property that is selected and returned, not to all properties with matching values.

```groovy
final Object vertexId = g.addV().id().next();

// Add two vertex properties with the same value (1) and meta-properties metafoo and metabar

g.V(vertexId).property(VertexProperty.Cardinality.list, "foo", 1, "metafoo", "foo", "metabar", "bar").next();

g.V(vertexId).property(VertexProperty.Cardinality.list, "foo", 1, "metafoo", "foo", "metabar", "bar").next();

g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1, "metafoo", "foo2", "metabaz", "baz").next();

// Result:

// Two vertex properties with {"foo":1}

// One vertex property has meta-properties {"metafoo":"foo", "metabar":"bar"}

// The other vertex property has meta-properties {"metafoo":"foo2", "metabar":"bar", "metabaz":"baz"}
```

## Set cardinality behavior

This section describes advanced behavior specific to `set` cardinality.

### Type compatibility in sets

Integer (`int`) and Long (`long`) values are considered comparable when determining uniqueness in a set. This means:

-   Adding a property with key `foo` and Integer value `123` does not add a new property if a property with key `foo` and Long value `123` already exists.
-   All other data types are compared strictly by type. For example, Double `1.0` is considered different from Integer `1`.

```groovy
final Object vertexId = g.addV().id().next();

g.V(vertexId).property("foo", 1).next();

g.V(vertexId).properties("foo").count().next(); // count == 1

g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1L).next();

g.V(vertexId).properties("foo").count().next(); // count == 1 (int and long are comparable)

g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1.0).next();

g.V(vertexId).properties("foo").count().next(); // count == 2 (double is not comparable to int)
```

::: caution
Date values are stored internally as epoch time (Long). If you use `set` cardinality on a property key that stores both Date and Integer/Long values, a collision can occur. This edge case throws an exception if the values collide. To avoid this issue, do not mix Date values with Integer or Long values under the same property key when using `set` cardinality.
:::

### Selection hierarchy for set operations

When you add a vertex property using `set` cardinality, two independent decisions occur:

1.  Uniqueness check: The database determines whether to add a new property based on type-comparable equality (int/long are comparable; other types are not).
2.  Property selection: AGS determines which existing property to return for subsequent traversal steps (such as adding meta-properties).

These decisions use different matching rules. For example, Double `123.00` does not trigger set uniqueness against Integer `123` (different types), but it does satisfy predicate matching for property selection.

AGS uses the following selection hierarchy to determine which property to return:

1.  Type equality: Properties with exact type and value match are selected first.
2.  Predicate matching: Properties that satisfy TinkerPop predicate logic (such as `hasValue()` or `has()`) are selected.
3.  Random selection: If multiple properties match at the same priority level, one is selected randomly.