Graph Importer

The GraphImporter is a high-performance, declarative graph importer that uses a two-pass CSR-first architecture to bulk-load graph data from XML, CSV, and JSONL files into ArcadeDB. It is designed for importing large datasets (millions of vertices and edges) with minimal memory overhead.

The importer is located in the integration module (com.arcadedb.integration.importer.graph.GraphImporter).

Never run the importer against a database directory that an ArcadeDB Server (or any other process) already has open. It opens the database files directly, bypassing the server, and two processes writing to the same database at once can corrupt it. To load data into a database a server is serving, use the server’s remote protocol instead, or stop the server for the duration of the import.

How It Works

The import runs in two passes:

  1. Pass 1 — Process each data source once: create vertices with full properties, collect graph topology as compressed int arrays

  2. Pass 2 — Create all edges from the in-memory topology using GraphBatch, one batch per edge type with bidirectional edges for full IN+OUT traversal

  3. Vector graphs — Build the graph of every LSM_VECTOR index on a type the import wrote to, so the index answers at index speed the moment run() returns (see Loading into a type with a vector index)

The speculative background maintenance of the database’s indexes (the vector index’s inactivity rebuild) is held off for the whole run, not only while one of the batches is open: a stall between the two passes must not be read as "the load is over".

Command-Line Usage

java -cp arcadedb-integration-*.jar com.arcadedb.integration.importer.graph.GraphImporter \
  <json-config-file> <database-path> [data-dir]
  • json-config-file — Path to the JSON configuration file (see JSON Configuration below)

  • database-path — Path where the database will be created (any existing database at this path is deleted)

  • data-dir — Optional base directory for resolving relative file paths in the JSON config (defaults to the JSON file’s parent directory)

Java API

The importer can also be used programmatically via a fluent Builder API:

try (GraphImporter importer = GraphImporter.builder(database)
    .vertex("User", new CsvRowSource("users.csv"), v -> {
        v.id("Id");
        v.intProperty("reputation", "Reputation");
        v.property("name", "DisplayName");
    })
    .vertex("Question", new XmlRowSource("posts.xml"), v -> {
        v.id("Id");
        v.filter("PostTypeId", "1");
        v.property("title", "Title");
        v.edgeIn("OwnerUserId", "ASKED", "User");
        v.splitEdge("Tags", "TAGGED_WITH", "Tag", "|");
    })
    .edgeSource("LINKED_TO", new CsvRowSource("links.csv"), e -> {
        e.from("PostId", "Question");
        e.to("RelatedId", "Question");
        e.intProperty("linkType", "LinkTypeId");
    })
    .limit(10000)
    .build()) {

  importer.run();
  System.out.printf("Vertices: %,d, Edges: %,d%n",
      importer.getVertexCount(), importer.getEdgeCount());
}

Or from a JSON configuration file:

String json = new String(Files.readAllBytes(jsonFile.toPath()));
JSONObject config = new JSONObject(json);

GraphImporter.createSchemaFromConfig(database, config);

try (GraphImporter importer = GraphImporter.fromJSON(database, config, dataDir)) {
  importer.run();
}

GraphImporter.executePostImportCommands(database, config);

JSON Configuration

The JSON configuration file defines vertex types, edge types, data sources, property mappings, and optional post-import commands.

Vertex Definitions

Each entry in the vertices array defines a vertex type and its data source:

Key Required Description

type

Yes

ArcadeDB vertex type name (auto-created if it does not exist)

file

Yes

Source file path, relative to the data directory. Format is auto-detected from extension: .xml, .csv, .jsonl

id

Yes

Source attribute used as the primary key for edge resolution between types. The value is taken as written, so an integer, a value too large for an integer, and a string such as W13696992 all work. Two spellings the file keeps apart stay two different keys: 007 and 7 are not the same vertex. An empty value registers no key

nameId

No

Secondary key, used by split edges and by byName edges to resolve values by name. Declare it only when a type is referenced through two different keys — a numeric id from one file and a name from another. A single string key needs nothing more than id

filter

No

Row filter in the format attribute=value. Only matching rows are imported. This enables splitting one file into multiple vertex types. An empty value (attribute=) selects the rows that do not set the attribute

element

No

For XML files: element name to read (defaults to row)

properties

No

Maps ArcadeDB property names to source attributes, optionally with a type prefix. See Property Types

edges

No

Array of edge definitions derived from foreign key attributes in this vertex’s source file

Property Types

A property mapping is "dbPropertyName": "sourceAttribute", where the source attribute may carry a type prefix. Without a prefix the value is imported as a string. The same prefixes are accepted in vertex properties and in edge-source properties.

Before 26.10.1 an edgeSources entry understood only int:, long: and double:, and quietly ignored any other mapping. If you have a configuration that declares other property types on an edge source, those properties are now actually imported.
An empty value means "not set" in every source format. Before 26.10.1 that was true only for CSV: the same blank column stored an empty string when the row came from a JSONL or XML file, so IS NULL and mandatory-property checks answered differently depending on which format the data had been exported to.
Prefix Stored type Notes

(none)

STRING

Null or empty source values are skipped

int:

INTEGER

Missing or empty values default to 0

long:

LONG

Missing or empty values default to 0

double:

DOUBLE

Missing or empty values default to 0.0

bool:

BOOLEAN

true when the value is True/true

datetime:

DATETIME

Default format yyyy-MM-dd HH:mm:ss. For a custom pattern use datetime:FORMAT|attribute, with both halves present

vector:

ARRAY_OF_FLOATS (float[])

A dense float vector (an embedding). JSONL reads the JSON array natively; CSV and XML parse the textual form [0.1,0.2,0.3]. See Arrays in a CSV file

list:

LIST

A generic array, e.g. a list of tags

Every prefix has to be followed by the name of the source attribute to read: a value such as "int:" on its own, or a datetime: pattern missing one half of its FORMAT|attribute pair, is reported as a configuration error. The same goes for a missing type, edge, target, attribute or file key: the message names the key, the source that declares it, and what the value is for. (Since v26.10.1: these were accepted and then matched nothing, so the import ran and the property was simply never filled in.)

Importing Vector Embeddings

Embedding pipelines usually emit JSONL with the vector inline as a JSON array:

{"id": 1, "title": "The Hitchhiker's Guide to the Galaxy", "tags": ["scifi", "comedy"],
 "embedding": [-0.31142, 0.51346, -0.02326, -0.29451, 0.57215]}

Declare the column with the vector: prefix and create the index afterwards with a post-import command:

{
  "vertices": [
    {
      "type": "Book", "file": "books.jsonl", "id": "id",
      "properties": {
        "title": "title",
        "tags": "list:tags",
        "embedding": "vector:embedding"
      }
    }
  ],
  "postImportCommands": [
    { "language": "sql",
      "command": "CREATE INDEX ON Book (embedding) LSM_VECTOR METADATA { dimensions: 5, similarity: 'COSINE' }" }
  ]
}

The property is stored as a primitive float[], not as a list of boxed numbers: a 768-dimension embedding costs roughly 3KB per vertex instead of 18KB, which matters on a bulk load of millions of rows, and it is the exact representation the vector index consumes, so building the index converts nothing.

Use vector: only for numeric arrays. list: keeps the array as a generic list and is the right choice for heterogeneous or non-numeric arrays such as tags.

The equivalent Java API calls are floatArrayProperty(name, attribute) and listProperty(name, attribute).

Loading into a type with a vector index

The index can also exist before the import. Every vector the load writes goes into the index’s delta buffer, where it is searchable by a linear scan, and the HNSW graph that makes the search fast is built afterwards. Left to the index, that build starts once the index has been quiet for its inactivity window, on a background thread - and a database.close() right after run(), which is what a loader normally does, cancels it and throws the work away.

So the importer builds it itself: when run() returns, every LSM_VECTOR index on a type the import wrote to has its graph built and persisted, and the process can close the database at once. The build is logged with its progress and takes as long as CREATE INDEX would over the same vectors. An index whose graph is already current is skipped.

To leave the build to the index’s background rebuild instead - only worth it when the database stays open long enough for that build to complete - set "vectorGraphBuild": false in the JSON configuration, or .withVectorGraphBuild(false) on the builder.

Arrays in a CSV file

CSV and XML have no array of their own, so an array column is read from its textual form [0.1,0.2,0.3]. In a CSV file, choose a delimiter the array does not contain - ; rather than the default , - because the CSV reader splits on the delimiter without honouring quotes, and a comma-delimited file would cut the array across several columns:

Id;Title;Embedding
1;The Hitchhiker's Guide to the Galaxy;[-0.31142,0.51346,-0.02326]

If the array is split anyway, the import stops and says so, naming the property and the delimiter.

Edge Definitions (within a vertex)

Each entry in a vertex’s edges array defines how to create edges from foreign key attributes:

Key Required Description

attribute

Yes

Source attribute containing the foreign key value

edge

Yes

ArcadeDB edge type name (auto-created if it does not exist)

target

Yes

Target vertex type the foreign key references

direction

No

out (default): this vertex → target. in: target → this vertex

split

No

Delimiter for multi-value fields (e.g., |). One edge is created per value, resolved by the target’s nameId

Edge-Only Sources

The edgeSources array defines edges where both endpoints already exist as vertices. No vertices are created from these sources:

Key Required Description

edge

Yes

ArcadeDB edge type name

file

Yes

Source file path

from

Yes

Compact format attribute:vertexType — source attribute and its vertex type. Matched against that type’s id, whatever the value looks like

to

Yes

Compact format attribute:vertexType — target attribute and its vertex type

properties

No

Property mappings (same format as vertex properties)

General Options

Key Required Description

limit

No

Maximum records per source (for testing). Omit or set to 0 for unlimited

vectorGraphBuild

No

Whether to build the graph of every LSM_VECTOR index on a type the import wrote to before run() returns (default true). See Loading into a type with a vector index

Post-Import Commands

The postImportCommands array defines commands to execute automatically after the graph import completes. This is useful for creating indexes, analytical views, or running any database command that depends on the imported data being present.

Key Required Description

language

Yes

Query language to use: sql, opencypher, etc.

command

Yes

The command text to execute

Commands are executed sequentially in the order they appear. If a command fails, a warning is logged and the remaining commands continue to execute.

If any post-import command triggers an asynchronous Graph Analytical View build, the importer automatically waits (up to 10 minutes) for all views to reach READY status before returning.

Example:

"postImportCommands": [
  {
    "language": "sql",
    "command": "CREATE INDEX ON Question (Id) UNIQUE"
  },
  {
    "language": "sql",
    "command": "CREATE GRAPH ANALYTICAL VIEW IF NOT EXISTS myGraph PROPERTIES (`!Body`, `!Text`) UPDATE MODE SYNCHRONOUS"
  }
]

Complete Example

Below is a complete JSON configuration for importing a StackOverflow data dump:

{
  "vertices": [
    {
      "type": "Tag", "file": "Tags.xml", "id": "Id", "nameId": "TagName",
      "properties": { "Id": "int:Id", "TagName": "TagName", "Count": "int:Count" }
    },
    {
      "type": "User", "file": "Users.xml", "id": "Id",
      "properties": {
        "Id": "int:Id", "DisplayName": "DisplayName", "Reputation": "int:Reputation",
        "CreationDate": "CreationDate", "Views": "int:Views"
      }
    },
    {
      "type": "Question", "file": "Posts.xml", "id": "Id", "filter": "PostTypeId=1",
      "properties": {
        "Id": "int:Id", "Title": "Title", "Body": "Body",
        "Score": "int:Score", "ViewCount": "int:ViewCount", "Tags": "Tags"
      },
      "edges": [
        { "attribute": "OwnerUserId", "edge": "ASKED", "target": "User", "direction": "in" },
        { "attribute": "Tags", "edge": "TAGGED_WITH", "target": "Tag", "split": "|" }
      ]
    },
    {
      "type": "Answer", "file": "Posts.xml", "id": "Id", "filter": "PostTypeId=2",
      "properties": {
        "Id": "int:Id", "Body": "Body", "Score": "int:Score"
      },
      "edges": [
        { "attribute": "OwnerUserId", "edge": "ANSWERED", "target": "User", "direction": "in" },
        { "attribute": "ParentId", "edge": "HAS_ANSWER", "target": "Question", "direction": "in" }
      ]
    }
  ],

  "edgeSources": [
    {
      "edge": "ACCEPTED_ANSWER", "file": "Posts.xml",
      "from": "Id:Question", "to": "AcceptedAnswerId:Answer"
    },
    {
      "edge": "LINKED_TO", "file": "PostLinks.xml",
      "from": "PostId:Question", "to": "RelatedPostId:Question",
      "properties": { "LinkType": "int:LinkTypeId" }
    }
  ],

  "postImportCommands": [
    {
      "language": "sql",
      "command": "CREATE GRAPH ANALYTICAL VIEW IF NOT EXISTS stackoverflow PROPERTIES (`!Body`, `!Text`) UPDATE MODE SYNCHRONOUS"
    }
  ]
}

Identities and Missing Edges

Every edge is resolved by matching the value of a foreign key against the id of the vertex type it points at. The importer compares those values as text, so any key your files already use works without being converted first:

{"id": "W13696992", "title": "A paper", "publication_year": 2008}
from_id,to_id
2257721487,28031933
Before 26.10.1 both of these failed: an identity had to fit an int, so a string key or one larger than about two billion aborted the import. Vertex ids of 0 were also unusable as an edge endpoint. No configuration change is needed to benefit from the fix.

When an edge names a key that no vertex carries, the edge is skipped — there is nothing to attach it to — but the importer now counts it and logs a warning per source, and the total is available from getUnresolvedEdgeCount() in the Java API. A graph that comes out smaller than the files you fed in no longer does so silently.

Four configurations that could only ever produce zero edges are now rejected with an error before anything is written, instead of importing a quietly incomplete graph:

  • an edge pointing at a vertex type that no source imports (usually a typo in the type name)

  • an edge pointing at a vertex type that is imported after the source referencing it. A vertex source can only resolve against types already imported, so declare the target’s source first

  • an edge resolving against the id of a type that declares only nameId, or the reverse

  • two vertex sources declaring the same type name

Supported File Formats

The file format is auto-detected from the file extension:

Extension Format

.xml

XML elements (configurable element name, defaults to row)

.csv

CSV with header row (first line defines property names)

.jsonl

JSON Lines (one JSON object per line)

Data Sources (Java API)

When using the Java API, you can use the following RecordSource implementations:

  • CsvRowSource — reads CSV files

  • XmlRowSource — reads XML files

  • JsonlRowSource — reads JSONL files

Custom data sources can be implemented via the RecordSource interface.