Native Select API
The Native Select API is a zero-parsing, JIT-friendly fluent query builder for embedded Java applications. It provides a type-safe alternative to SQL or Cypher for common query patterns, with automatic index detection and significantly lower overhead.
Quick Start
// Simple query
List<Vertex> results = database.select().fromType("Person")
.where().property("age").gt().value(18)
.vertices().toList();
// Compiled query with parameters (reusable)
SelectCompiled query = database.select().fromType("Person")
.where().property("name").eq().parameter("name")
.compile();
Vertex john = query.parameter("name", "John").vertices().nextOrNull();
Vertex jane = query.parameter("name", "Jane").vertices().nextOrNull();
Building Queries
From Type or Buckets
Every query starts with database.select() followed by the source:
// Query from a type (polymorphic by default)
database.select().fromType("Person")...
// Disable polymorphic query (only the exact type, not subtypes)
database.select().fromType("Person").polymorphic(false)...
// Query from specific buckets
database.select().fromBuckets("person_bucket_0", "person_bucket_1")...
WHERE Conditions
The where() method starts the fluent condition builder.
Each condition follows the pattern: property(name) → operator() → value(val).
database.select().fromType("Person")
.where().property("age").gt().value(18)
.and().property("active").eq().value(true)
.vertices().toList();
Comparison Operators
| Method | Description | Example |
|---|---|---|
|
Equals |
|
|
Not equals |
|
|
Less than |
|
|
Less than or equal |
|
|
Greater than |
|
|
Greater than or equal |
|
|
Pattern match (case-sensitive) |
|
|
Pattern match (case-insensitive) |
|
|
Value in collection |
|
|
Value in range (inclusive) |
|
|
Property is null (unary) |
|
|
Property is not null (unary) |
|
IN Operator
The in() operator checks if a property value is contained in a collection.
When the property is indexed, ArcadeDB performs multiple point lookups on the index.
// Find persons with specific statuses
List<Vertex> results = database.select().fromType("Person")
.where().property("status").in().value(List.of("active", "pending"))
.vertices().toList();
// IN with parameterized values
SelectCompiled query = database.select().fromType("Order")
.where().property("status").in().parameter("statuses")
.compile();
List<Document> orders = query.parameter("statuses", List.of("shipped", "delivered"))
.documents().toList();
BETWEEN Operator
The between() operator checks if a value falls within an inclusive range.
When the property is indexed, ArcadeDB uses a range scan on the index.
// Find orders with amount between 100 and 500
List<Document> results = database.select().fromType("Order")
.where().property("amount").between().values(100, 500)
.documents().toList();
IS NULL / IS NOT NULL
These are unary operators — they don’t require a right-hand value.
// Find records where email is not set
List<Document> results = database.select().fromType("Person")
.where().property("email").isNull()
.documents().toList();
// Combine with other conditions
List<Vertex> active = database.select().fromType("Person")
.where().property("email").isNotNull()
.and().property("age").gt().value(18)
.vertices().toList();
AND / OR Logic
Conditions can be combined with and() and or().
AND has higher precedence than OR (same as SQL).
// AND: both conditions must match
database.select().fromType("Person")
.where().property("age").gt().value(18)
.and().property("active").eq().value(true)
.vertices();
// OR: either condition matches
database.select().fromType("Person")
.where().property("role").eq().value("admin")
.or().property("role").eq().value("manager")
.vertices();
// Mixed: AND binds tighter than OR
// Equivalent to: (age > 18 AND active = true) OR role = 'admin'
database.select().fromType("Person")
.where().property("age").gt().value(18)
.and().property("active").eq().value(true)
.or().property("role").eq().value("admin")
.vertices();
Terminal Operations
Terminal methods execute the query and return results.
Iterator-Based Results
// Returns SelectIterator<Vertex>
SelectIterator<Vertex> iter = database.select().fromType("Person")
.where().property("active").eq().value(true)
.vertices();
// Returns SelectIterator<Edge>
SelectIterator<Edge> iter = database.select().fromType("Follows")
.where().property("since").gt().value(2020)
.edges();
// Returns SelectIterator<Document>
SelectIterator<Document> iter = database.select().fromType("Log")
.where().property("level").eq().value("ERROR")
.documents();
SelectIterator extends Iterator and adds:
-
toList()— collects all results into aList -
nextOrNull()— returns the next element ornullif empty -
stream()— returns a JavaStreamfor functional-style processing -
getMetrics()— returns execution metrics (evaluated records, used indexes) -
outVertices()/inVertices()/bothVertices()— traverse to adjacent vertices (see Graph Traversal) -
outEdges()/inEdges()/bothEdges()— traverse to adjacent edges (see Graph Traversal)
count()
Returns the number of matching records without materializing them.
Respects skip() and limit().
long total = database.select().fromType("Person")
.where().property("active").eq().value(true)
.count();
// With limit
long capped = database.select().fromType("Person")
.where().property("active").eq().value(true)
.limit(100).count();
// From compiled query
SelectCompiled query = database.select().fromType("Order")
.where().property("status").eq().parameter("status")
.compile();
long pending = query.parameter("status", "pending").count();
exists()
Returns true if at least one matching record exists.
Short-circuits immediately on the first match (does not scan further).
boolean hasAdmins = database.select().fromType("Person")
.where().property("role").eq().value("admin")
.exists();
stream()
Returns a standard Java Stream<Document> for functional-style processing.
List<String> names = database.select().fromType("Person")
.where().property("age").gt().value(18)
.stream()
.map(d -> d.getString("name"))
.collect(Collectors.toList());
// From iterator (preserves the generic type)
Optional<Vertex> first = database.select().fromType("Person")
.where().property("name").eq().value("Alice")
.vertices().stream()
.findFirst();
Pagination and Ordering
Skip and Limit
// Skip the first 20, return the next 10
List<Vertex> page = database.select().fromType("Person")
.where().property("active").eq().value(true)
.skip(20).limit(10)
.vertices().toList();
Order By
// Sort ascending by name
List<Vertex> sorted = database.select().fromType("Person")
.where().property("active").eq().value(true)
.orderBy("name", true) // true = ascending
.vertices().toList();
// Sort descending by age
List<Vertex> sorted = database.select().fromType("Person")
.orderBy("age", false) // false = descending
.vertices().toList();
When the ORDER BY property matches an indexed property used in the WHERE condition, ArcadeDB reads results directly from the index in sorted order, avoiding an in-memory sort.
Compiled Queries
Compiled queries separate query definition from execution. The query structure is built once, then reused with different parameter values.
// Compile once
SelectCompiled query = database.select().fromType("Person")
.where().property("name").eq().parameter("name")
.and().property("city").eq().parameter("city")
.compile();
// Execute many times with different parameters
Vertex v1 = query.parameter("name", "John").parameter("city", "NYC").vertices().nextOrNull();
Vertex v2 = query.parameter("name", "Jane").parameter("city", "LA").vertices().nextOrNull();
// All terminal methods are available on compiled queries
long count = query.parameter("name", "John").parameter("city", "NYC").count();
boolean exists = query.parameter("name", "John").parameter("city", "NYC").exists();
Stream<Document> stream = query.parameter("name", "John").parameter("city", "NYC").stream();
JSON Serialization
Compiled queries can be serialized to/from JSON for persistence or network transfer:
SelectCompiled original = database.select().fromType("Person")
.where().property("name").eq().parameter("name")
.compile();
// Serialize to JSON
JSONObject json = original.json();
// Deserialize from JSON
SelectCompiled restored = database.select().json(json).compile();
Vertex v = restored.parameter("name", "John").vertices().nextOrNull();
Parallel Execution
For large datasets, enable parallel execution to distribute bucket scanning across multiple threads:
SelectCompiled query = database.select().fromType("LargeTable")
.where().property("status").eq().value("active")
.compile()
.parallel();
List<Document> results = query.documents().toList();
| Parallel queries cannot use the current transaction context because they execute in separate threads. Use them for read-only queries outside transactions. |
Timeout
Set a maximum execution time for queries:
// Timeout after 5 seconds, throw TimeoutException
database.select().fromType("Person")
.where().property("name").like().value("%pattern%")
.timeout(5, TimeUnit.SECONDS, true)
.documents().toList();
// Timeout after 5 seconds, silently return partial results
database.select().fromType("Person")
.where().property("name").like().value("%pattern%")
.timeout(5, TimeUnit.SECONDS, false)
.documents().toList();
Index Detection
The Select API automatically detects and leverages available indexes:
-
Equality (
eq) — single-key index lookup -
Range (
gt,ge,lt,le) — range scan on the index -
IN (
in) — multiple point lookups on the index -
BETWEEN (
between) — range scan with inclusive bounds -
AND logic — uses the best available index (prefers unique indexes)
-
OR logic — uses both indexes when both sides are indexed; falls back to full scan when either side is not indexed
Operators that cannot use indexes (neq, like, ilike, isNull, isNotNull) always require a full scan (but can still benefit from indexes on other conditions in an AND chain).
Use getMetrics() on the iterator to verify index usage:
SelectIterator<Vertex> iter = database.select().fromType("Person")
.where().property("id").eq().value(42)
.vertices();
Vertex result = iter.nextOrNull();
Map<String, Object> metrics = iter.getMetrics();
long evaluatedRecords = (long) metrics.get("evaluatedRecords");
int usedIndexes = (int) metrics.get("usedIndexes");
Graph Traversal
The Select API supports graph traversal directly from query results.
ArcadeDB is optimized to skip edges when you only need vertices — use outVertices() / inVertices() / bothVertices() for vertex-to-vertex traversal, and outEdges() / inEdges() / bothEdges() when you need the edge records themselves.
Vertex Traversal
// Single hop: find all friends of Alice
List<Vertex> friends = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.outVertices("FRIENDS")
.toList();
// Multi-hop: friends of friends
Set<String> for = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.outVertices("FRIENDS")
.thenOutVertices("FRIENDS")
.stream()
.map(v -> v.getString("name"))
.collect(Collectors.toSet());
// Incoming direction
List<Vertex> followers = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.inVertices("FOLLOWS")
.toList();
// Both directions
List<Vertex> connections = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.bothVertices("KNOWS")
.toList();
// No edge type filter (all edge types)
List<Vertex> allNeighbors = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.outVertices()
.toList();
SelectVertexTraversal implements Iterator<Vertex> and provides:
-
thenOutVertices(edgeTypes…)— chain another outgoing vertex traversal -
thenInVertices(edgeTypes…)— chain another incoming vertex traversal -
thenBothVertices(edgeTypes…)— chain another bidirectional vertex traversal -
thenOutEdges(edgeTypes…)— switch to edge traversal (outgoing) -
thenInEdges(edgeTypes…)— switch to edge traversal (incoming) -
thenBothEdges(edgeTypes…)— switch to edge traversal (both directions) -
toList()— collect results into aList<Vertex> -
stream()— return a JavaStream<Vertex>
Edge Traversal
Use edge traversal when you need the edge records (e.g., to read edge properties):
// Get outgoing FRIENDS edges
List<Edge> edges = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.outEdges("FRIENDS")
.toList();
// Access edge properties
for (Edge edge : edges) {
System.out.println(edge.getString("since"));
}
// Vertex-to-edge chaining: find friends, then get their WORKS_AT edges
List<Edge> worksAt = database.select().fromType("User")
.where().property("name").eq().value("Alice")
.vertices()
.outVertices("FRIENDS")
.thenOutEdges("WORKS_AT")
.toList();
SelectEdgeTraversal implements Iterator<Edge> and provides:
-
toList()— collect results into aList<Edge> -
stream()— return a JavaStream<Edge>
Vector k-NN Search
The Select API provides a fluent interface for vector similarity search. It requires a vector index on the target property.
float[] queryVector = getEmbedding("search query");
// Basic k-NN search: find 10 nearest neighbors
List<SelectVectorResult<Vertex>> results = database.select()
.fromType("Product")
.nearestTo("embedding", queryVector, 10)
.vertices();
// Access results
for (SelectVectorResult<Vertex> result : results) {
Vertex doc = result.getDocument(); // the matched vertex
float distance = result.getDistance(); // distance from query vector
}
Results are returned as a List (not an iterator) sorted by ascending distance, because vector search is inherently bounded by k and requires cross-bucket merging.
Approximate Search
For large datasets, enable approximate nearest neighbor search using product quantization:
List<SelectVectorResult<Vertex>> results = database.select()
.fromType("Product")
.nearestTo("embedding", queryVector, 10)
.approximate(true)
.vertices();
Post-Filtering
Apply WHERE conditions to filter vector search results:
// Find 50 nearest, then filter to only "electronics" category
List<SelectVectorResult<Vertex>> results = database.select()
.fromType("Product")
.nearestTo("embedding", queryVector, 50)
.where().property("category").eq().value("electronics")
.vectorVertices();
| Post-filtering is applied after the k-NN search. To ensure enough results after filtering, request more candidates than you need (e.g., request 50 to get ~10 after filtering). |
Complete Example
try (DatabaseFactory factory = new DatabaseFactory("./mydb")) {
Database database;
if (factory.exists())
database = factory.open();
else
database = factory.create();
// Create schema
database.getSchema().createVertexType("Person")
.createProperty("name", Type.STRING)
.createIndex(Schema.INDEX_TYPE.LSM_TREE, true);
database.getSchema().createVertexType("Person")
.createProperty("age", Type.INTEGER);
database.getSchema().createEdgeType("FRIENDS");
// Insert data
database.transaction(() -> {
Vertex alice = database.newVertex("Person").set("name", "Alice", "age", 30).save();
Vertex bob = database.newVertex("Person").set("name", "Bob", "age", 25).save();
alice.newEdge("FRIENDS", bob).save();
});
// Query with the Select API
// Count
long adults = database.select().fromType("Person")
.where().property("age").ge().value(18).count();
// Check existence
boolean hasAlice = database.select().fromType("Person")
.where().property("name").eq().value("Alice").exists();
// Stream processing
List<String> names = database.select().fromType("Person")
.where().property("age").gt().value(20)
.stream()
.map(d -> d.getString("name"))
.sorted()
.collect(Collectors.toList());
// Graph traversal
List<Vertex> aliceFriends = database.select().fromType("Person")
.where().property("name").eq().value("Alice")
.vertices()
.outVertices("FRIENDS")
.toList();
database.close();
}