Kastor
Kastor

Not another triple-store wrapper

RDF that fits how Kotlin apps are built

Domain-first Kotlin: keep business types clean, reach RDF through an explicit side channel—DSL, handles, and optional KSP codegen. RDF 1.2 and SHACL 1.2 are first-class; swap Jena, RDF4J, memory, or SPARQL endpoints with configuration. Kastor is an API layer—not a database; providers do storage and protocol heavy lifting.

What makes Kastor different

Kotlin-first boundaries and explicit RDF access—instead of spreading Model / vendor graph types through your core.

Goal Kastor Typical RDF APIs alone
Domain code without RDF in the core Pure Kotlin interfaces; RDF via handles & codegen Often couples logic to graph APIs
Shapes & vocabs drive APIs DSL + Kastor Gen (SHACL / JSON-LD → Kotlin) Manual boilerplate or ad hoc mapping
Swap engines without rewriting the app Same patterns over Memory, Jena, RDF4J, SPARQL Usually one stack’s surface area
Standards depth (1.2 track) RDF 1.2 suites, native SHACL 1.2— conformance, SHACL Often uneven 1.2 / SHACL coverage in one Kotlin story

Scope and tradeoffs: architecture, documentation hub.

Hello RDF in a few lines

DSL in, SELECT out—same pattern across in-memory or remote backends.

import com.geoknoesis.kastor.rdf.*

fun main() {
    val repo = Rdf.memory()
    repo.add {
        val alice = iri("http://example.org/alice")
        alice has "http://xmlns.com/foaf/0.1/name" with "Alice"
        alice has "http://xmlns.com/foaf/0.1/knows" with iri("http://example.org/bob")
    }
    val q =
        "SELECT ?name WHERE { <http://example.org/alice> <http://xmlns.com/foaf/0.1/name> ?name . }"
    repo.select(SparqlSelectQuery(q)).forEach { println(it.getString("name")) }
}

Documentation

Community

Discussions · Issues · Email · Sponsor