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.
- JDK 21 or later
- Maven dependency:
<dependency> <groupId>io.youtrackdb</groupId> <artifactId>youtrackdb-embedded</artifactId> <version>0.5.0-SNAPSHOT</version> </dependency>
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.
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:
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 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:
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 1978A 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
}
});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
});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:
The MATCH statement is the recommended way to traverse graphs in YQL. It uses
declarative patterns to find connected vertices.
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 GodfatherThe pattern reads naturally: start at a Person vertex, follow an outgoing
ActedIn edge (-ActedIn->), and arrive at a Movie vertex.
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.
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.
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.
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
});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:
// 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
YouTrackDB supports class inheritance — including multiple inheritance. Querying a parent class automatically returns instances of all its subclasses.
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");
});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");
});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)
Sequences generate auto-incrementing numeric values — useful for invoice numbers, ticket IDs, or any counter that must be unique across records.
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.
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: InitechYou 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:
- 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