Getting Started with YouTrackDB
September 18, 2026 · View on GitHub
This guide walks you through the primary features of YouTrackDB using YQL (YouTrackDB Query Language). YQL is a SQL-based query language with extensions for graph functionality — if you know SQL, you already know most of YQL.
All examples use the public API and run as embedded in-memory databases.
The complete source code is in
GettingStartedExample.java,
with tests in
ExamplesTest.java.
Prerequisites
- JDK 21 or later
- Maven dependency:
<dependency> <groupId>io.youtrackdb</groupId> <artifactId>youtrackdb-embedded</artifactId> <version>0.5.0-SNAPSHOT</version> </dependency>
1. Create a Database
try (var ytdb = YourTracks.instance(".")) {
ytdb.create("getting-started", DatabaseType.MEMORY, "admin", "adminpwd", "admin");
try (var g = ytdb.openTraversal("getting-started", "admin", "adminpwd")) {
// All operations go here — g is a YTDBGraphTraversalSource
}
}
YourTracks.instance(".") creates an embedded YouTrackDB instance. The path is the
root directory for database storage (not significant for in-memory databases).
openTraversal() returns a YTDBGraphTraversalSource — the entry point for both
Gremlin traversals and YQL queries.
2. Define a Schema
Classes in YouTrackDB are analogous to tables in a relational database.
Vertex classes extend V, edge classes extend E.
g.executeInTx(tx -> {
tx.command("CREATE CLASS Person EXTENDS V");
tx.command("CREATE CLASS Movie EXTENDS V");
tx.command("CREATE CLASS ActedIn EXTENDS E");
tx.command("CREATE CLASS Directed EXTENDS E");
});
Properties define the schema. Constraints like MANDATORY and MIN enforce
data integrity at the database level.
g.executeInTx(tx -> {
tx.command("CREATE PROPERTY Person.name STRING (MANDATORY TRUE, MIN 1)");
tx.command("CREATE PROPERTY Person.birthYear INTEGER");
tx.command("CREATE PROPERTY Movie.title STRING (MANDATORY TRUE)");
tx.command("CREATE PROPERTY Movie.year INTEGER");
tx.command("CREATE PROPERTY Movie.genre STRING");
tx.command("CREATE PROPERTY ActedIn.role STRING");
});
Indexes accelerate lookups. UNIQUE enforces uniqueness; NOTUNIQUE
allows duplicates.
g.executeInTx(tx -> {
tx.command("CREATE INDEX Person.name ON Person (name) UNIQUE");
tx.command("CREATE INDEX Movie.title ON Movie (title) UNIQUE");
tx.command("CREATE INDEX Movie.year ON Movie (year) NOTUNIQUE");
});
For the full YQL reference, see:
3. Insert Data
Vertices
Use CREATE VERTEX to add records. The SET clause defines property values.
g.executeInTx(tx -> {
tx.yql("CREATE VERTEX Person SET name = 'Alice', birthYear = 1990").iterate();
tx.yql("CREATE VERTEX Person SET name = 'Bob', birthYear = 1985").iterate();
tx.yql("CREATE VERTEX Person SET name = 'Charlie', birthYear = 1978").iterate();
tx.yql("CREATE VERTEX Movie SET title = 'The Matrix', year = 1999, genre = 'Sci-Fi'").iterate();
tx.yql("CREATE VERTEX Movie SET title = 'Inception', year = 2010, genre = 'Sci-Fi'").iterate();
tx.yql("CREATE VERTEX Movie SET title = 'The Godfather', year = 1972, genre = 'Crime'").iterate();
});
Edges
Edges connect vertices. Use sub-queries in FROM and TO to select the endpoints.
g.executeInTx(tx -> {
tx.yql("CREATE EDGE ActedIn "
+ "FROM (SELECT FROM Person WHERE name = 'Alice') "
+ "TO (SELECT FROM Movie WHERE title = 'The Matrix') "
+ "SET role = 'Trinity'").iterate();
tx.yql("CREATE EDGE ActedIn "
+ "FROM (SELECT FROM Person WHERE name = 'Alice') "
+ "TO (SELECT FROM Movie WHERE title = 'Inception') "
+ "SET role = 'Ariadne'").iterate();
tx.yql("CREATE EDGE ActedIn "
+ "FROM (SELECT FROM Person WHERE name = 'Bob') "
+ "TO (SELECT FROM Movie WHERE title = 'The Matrix') "
+ "SET role = 'Neo'").iterate();
tx.yql("CREATE EDGE ActedIn "
+ "FROM (SELECT FROM Person WHERE name = 'Charlie') "
+ "TO (SELECT FROM Movie WHERE title = 'The Godfather') "
+ "SET role = 'Michael'").iterate();
tx.yql("CREATE EDGE Directed "
+ "FROM (SELECT FROM Person WHERE name = 'Charlie') "
+ "TO (SELECT FROM Movie WHERE title = 'Inception')").iterate();
});
For the full YQL reference, see:
4. Query Data
Projections
A SELECT with projections returns Map<String, Object> results:
g.executeInTx(tx -> {
var people = tx.yql(
"SELECT name, birthYear FROM Person ORDER BY name").toList();
for (var row : people) {
var map = (Map<String, Object>) row;
System.out.println(map.get("name") + ", born " + map.get("birthYear"));
}
});
// Alice, born 1990
// Bob, born 1985
// Charlie, born 1978
Full Records
A SELECT without projections returns Vertex objects:
g.executeInTx(tx -> {
var scifi = tx.yql(
"SELECT FROM Movie WHERE genre = 'Sci-Fi' AND year >= 2000").toList();
for (var row : scifi) {
var v = (Vertex) row;
System.out.println(v.value("title")); // Inception
}
});
Filtering with WHERE
YQL supports familiar SQL conditions — comparisons, LIKE, BETWEEN, IN,
CONTAINS, and more:
g.executeInTx(tx -> {
var young = tx.yql(
"SELECT name FROM Person WHERE birthYear > 1980 ORDER BY name").toList();
// Returns: Alice, Bob
});
Aggregation
Standard SQL aggregation functions work as expected:
g.executeInTx(tx -> {
var result = tx.yql("SELECT COUNT(*) AS cnt FROM Person").next();
var map = (Map<String, Object>) result;
System.out.println("Total: " + map.get("cnt")); // Total: 3
});
For the full YQL reference, see:
5. Graph Pattern Matching with MATCH
The MATCH statement is the recommended way to traverse graphs in YQL. It uses
declarative patterns to find connected vertices.
Basic Pattern: Actors and Movies
g.executeInTx(tx -> {
var matches = tx.yql(
"MATCH {class: Person, as: actor}"
+ " -ActedIn-> {class: Movie, as: movie}"
+ " RETURN actor.name AS actor, movie.title AS movie"
+ " ORDER BY actor, movie")
.toList();
for (var row : matches) {
var map = (Map<String, Object>) row;
System.out.println(map.get("actor") + " in " + map.get("movie"));
}
});
// Alice in Inception
// Alice in The Matrix
// Bob in The Matrix
// Charlie in The Godfather
The pattern reads naturally: start at a Person vertex, follow an outgoing
ActedIn edge (-ActedIn->), and arrive at a Movie vertex.
Multi-hop Traversal: Co-actors
Patterns can chain multiple hops. This finds people who acted in the same movie as Alice:
g.executeInTx(tx -> {
var coActors = tx.yql(
"MATCH {class: Person, as: alice, where: (name = 'Alice')}"
+ " -ActedIn-> {class: Movie, as: movie}"
+ " <-ActedIn- {class: Person, as: coactor,"
+ " where: (name <> 'Alice')}"
+ " RETURN DISTINCT coactor.name AS coactor,"
+ " movie.title AS movie"
+ " ORDER BY coactor, movie")
.toList();
// Returns: Bob in The Matrix
});
The pattern traverses two hops: Alice → Movie → co-actor, using <-ActedIn-
for the reverse direction.
For the full MATCH reference, see MATCH.
6. Update Data
g.executeInTx(tx -> {
tx.yql("UPDATE Movie SET genre = 'Sci-Fi/Thriller'"
+ " WHERE title = 'Inception'").iterate();
});
UPDATE supports SET, REMOVE, CONTENT (replace entire record), and
MERGE (merge JSON into record). See UPDATE.
7. Delete Data
g.executeInTx(tx -> {
tx.yql("DELETE EDGE Directed"
+ " FROM (SELECT FROM Person WHERE name = 'Charlie')"
+ " TO (SELECT FROM Movie WHERE title = 'Inception')").iterate();
});
Always delete edges before deleting vertices that they connect to.
DELETE VERTEX automatically removes all connected edges.
See DELETE VERTEX and
DELETE EDGE.
8. Parameterized Queries
Use :param placeholders and pass values as key-value pairs. This prevents
injection and improves query plan caching.
g.executeInTx(tx -> {
var result = tx.yql(
"SELECT name, birthYear FROM Person WHERE name = :name",
"name", "Bob").next();
var map = (Map<String, Object>) result;
System.out.println(map.get("name") + ", born " + map.get("birthYear"));
// Bob, born 1985
});
9. Transactions
All data operations in YouTrackDB are transactional and run under snapshot isolation by default. Each transaction sees a consistent snapshot of the database as of its start time. Dirty reads, non-repeatable reads, and phantom reads are eliminated automatically.
The executeInTx() method starts, commits, and rolls back transactions. Supported schema data
definition language (DDL) statements include CREATE, ALTER, and DROP for classes and properties,
plus CREATE and DROP INDEX. These schema changes join the same transaction as data changes. They
become durable together on commit and disappear together on rollback.
The command() method executes immediately. The yql() method is lazy and requires iterate() or
another terminal operation. When either method executes without a caller transaction, the schema
change is persisted immediately.
// Read-write transaction — commits on success, rolls back on exception
g.executeInTx(tx -> {
tx.yql("CREATE VERTEX Person SET name = 'Diana', birthYear = 1995").iterate();
});
// Read-only transaction that returns a result
var count = g.computeInTx(tx ->
((Map<String, Object>) tx.yql("SELECT COUNT(*) AS cnt FROM Person").next())
.get("cnt"));
executeInTx()— runs code in a transaction without returning a valuecomputeInTx()— runs code in a transaction and returns a result
10. Inheritance and Polymorphic Queries
YouTrackDB supports class inheritance — including multiple inheritance. Querying a parent class automatically returns instances of all its subclasses.
Abstract Base Classes
g.executeInTx(tx -> {
// Abstract classes cannot be instantiated directly.
tx.command("CREATE CLASS Animal EXTENDS V ABSTRACT");
tx.command("CREATE PROPERTY Animal.name STRING (MANDATORY TRUE)");
tx.command("CREATE PROPERTY Animal.sound STRING");
// Concrete subclasses inherit all properties from Animal.
tx.command("CREATE CLASS Dog EXTENDS Animal");
tx.command("CREATE PROPERTY Dog.breed STRING");
tx.command("CREATE CLASS Cat EXTENDS Animal");
tx.command("CREATE PROPERTY Cat.indoor BOOLEAN");
});
Multiple Inheritance
A class can extend more than one superclass. It inherits properties from all of them.
g.executeInTx(tx -> {
tx.command("CREATE CLASS Pet EXTENDS V ABSTRACT");
tx.command("CREATE PROPERTY Pet.ownerName STRING");
// HouseDog inherits from both Dog and Pet.
tx.command("CREATE CLASS HouseDog EXTENDS Dog, Pet");
});
Polymorphic Queries
Querying the base class returns all subclass instances:
g.executeInTx(tx -> {
tx.yql("CREATE VERTEX Dog SET name = 'Rex', sound = 'Woof', breed = 'Shepherd'").iterate();
tx.yql("CREATE VERTEX Cat SET name = 'Whiskers', sound = 'Meow', indoor = true").iterate();
tx.yql("CREATE VERTEX HouseDog SET name = 'Buddy', sound = 'Bark',"
+ " breed = 'Labrador', ownerName = 'Alice'").iterate();
});
// SELECT FROM Animal returns Dog, Cat, and HouseDog instances.
g.executeInTx(tx -> {
var animals = tx.yql("SELECT name, @class AS cls FROM Animal ORDER BY name").toList();
// Buddy (HouseDog), Rex (Dog), Whiskers (Cat)
});
Querying a subclass directly returns only its instances (including further
subclasses). SELECT FROM Dog returns Dog and HouseDog but not Cat:
g.executeInTx(tx -> {
var dogs = tx.yql("SELECT name FROM Dog ORDER BY name").toList();
// Buddy, Rex
});
Multiple inheritance means HouseDog also appears in Pet queries:
g.executeInTx(tx -> {
var pets = tx.yql("SELECT name, ownerName FROM Pet ORDER BY name").toList();
// Buddy, owner=Alice
});
For a deeper dive into property types, schema evolution, and edge inheritance, see the Object-Oriented Data Modeling guide.
For the full YQL reference, see:
- CREATE CLASS (including
ABSTRACTandEXTENDS) - ALTER CLASS (change superclasses at runtime)
11. Sequences
Sequences generate auto-incrementing numeric values — useful for invoice numbers, ticket IDs, or any counter that must be unique across records.
Creating a Sequence
g.executeInTx(tx -> {
tx.command("CREATE SEQUENCE invoiceSeq TYPE ORDERED");
});
ORDERED guarantees strictly incrementing values with no gaps. CACHED
pre-allocates blocks of values for higher throughput, but may introduce gaps
after a restart.
Auto-Generated IDs with Property Defaults
Use a sequence in a property's default expression to auto-assign values
whenever a vertex is created:
g.executeInTx(tx -> {
tx.command("CREATE CLASS Invoice EXTENDS V");
tx.command("CREATE PROPERTY Invoice.invoiceNo LONG"
+ " (MANDATORY TRUE, default \"sequence('invoiceSeq').next()\")");
tx.command("CREATE PROPERTY Invoice.customer STRING (MANDATORY TRUE)");
tx.command("CREATE PROPERTY Invoice.amount DOUBLE");
tx.command("CREATE INDEX Invoice.invoiceNo ON Invoice (invoiceNo) UNIQUE");
});
Now every Invoice vertex gets an invoiceNo automatically:
g.executeInTx(tx -> {
tx.yql("CREATE VERTEX Invoice SET customer = 'Acme Corp', amount = 1500.00").iterate();
tx.yql("CREATE VERTEX Invoice SET customer = 'Globex Inc', amount = 2300.50").iterate();
tx.yql("CREATE VERTEX Invoice SET customer = 'Initech', amount = 750.00").iterate();
});
g.executeInTx(tx -> {
var invoices = tx.yql(
"SELECT invoiceNo, customer FROM Invoice ORDER BY invoiceNo").toList();
for (var row : invoices) {
var map = (Map<String, Object>) row;
System.out.println(map.get("invoiceNo") + ": " + map.get("customer"));
}
});
// 1: Acme Corp
// 2: Globex Inc
// 3: Initech
Using sequence().next() in Statements
You can also call sequence().next() directly in UPDATE statements:
g.executeInTx(tx -> {
tx.command("CREATE SEQUENCE ticketSeq TYPE CACHED START 1000 INCREMENT 1 CACHE 20");
tx.command("CREATE CLASS Ticket EXTENDS V");
tx.command("CREATE PROPERTY Ticket.ticketNo LONG");
tx.command("CREATE PROPERTY Ticket.summary STRING (MANDATORY TRUE)");
});
g.executeInTx(tx -> {
tx.yql("CREATE VERTEX Ticket SET summary = 'Login bug'").iterate();
});
g.executeInTx(tx -> {
tx.yql("UPDATE Ticket SET ticketNo = sequence('ticketSeq').next()"
+ " WHERE summary = 'Login bug'").iterate();
});
For the full YQL reference, see:
Next Steps
- Fine-Grained Security — predicate-based security policies, per-role filtering
- YQL Introduction — overview of the YQL dialect
- YQL Commands Reference — complete list of all YQL commands
- MATCH Statement — full guide to graph pattern matching
- YQL Functions — built-in functions (graph, math, collections)
- EXPLAIN and PROFILE — query analysis tools