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 value
  • computeInTx() — 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:

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