Kastor Gen Documentation
Welcome to the Kastor Gen documentation. Kastor Gen is the Kotlin code-generation layer for Kastor: it turns SHACL shapes and JSON-LD context into pure domain interfaces plus RDF-backed wrappers, wired together at runtime by OntoMapper.
Shared definitions (shape, literal, repository, materialization in the codegen sense vs reasoning): Glossary. How tutorials differ from reference across the site: How documentation fits together.
Navigate: tutorials explain workflows;
reference/is the API source of truth; Examples summarizes sample docs; for a runnable DCAT-style build, use theexamples/dcat-usGradle module on GitHub (see also Kastor examples).
Why Kastor Gen?
The Problem
Writing domain interfaces manually is:
- ❌ Time-consuming: 1-2 hours per complex class
- ❌ Error-prone: Typos, wrong IRIs, type mismatches
- ❌ Hard to maintain: Must manually sync with ontology changes
- ❌ Inconsistent: Code can drift from ontology definitions
The Solution
Kastor Gen generates type-safe interfaces automatically:
- ✅ 90% faster: 2 minutes vs 1-2 hours per class
- ✅ 100% consistent: Code always matches ontology
- ✅ Type-safe: Compile-time validation from SHACL constraints
- ✅ Easy updates: Regenerate when ontology changes
Key Benefits
| Benefit | Impact |
|---|---|
| 90% less manual code | Faster development |
| 100% consistency | Zero sync errors |
| Compile-time safety | Fewer runtime bugs |
| Single source of truth | Easier maintenance |
| Pure domain objects | Clean business code |
| See detailed benefits → | View comparisons → |
Table of Contents
- Getting Started - Quick start guide
- Core Concepts - Understanding the architecture
- Domain Modeling - Creating domain interfaces
- Prefix Mappings - Using QNames and prefix mappings
- RDF Integration - Working with RDF side-channels
- Ontology Generation - Generating code from SHACL/JSON-LD
- Gradle Configuration - Gradle-only ontology generation
- Gradle Plugin Reference - Complete Gradle plugin documentation
- Incremental Builds - Understanding KSP task inputs/outputs and incremental compilation
- Serializing Domain Instances - Serializing generated interface instances to RDF formats
- API Reference - Detailed API documentation
- Examples - Sample applications and use cases
- Best Practices - Guidelines for effective usage
- FAQ - Frequently asked questions
flowchart LR
Ontology["SHACL + JSON-LD"] --> KSP["KSP processor"]
KSP --> Dom["Domain interfaces"]
KSP --> Wrap["Wrappers (lazy)\nOntoMapper.register"]
KSP --> DC["Data classes (eager)\n+ Factory objects"]
Graph["RdfGraph / RdfRepository"] --> Mat["materialize / asType"]
Wrap --> Mat
DC --> Mat
Mat --> Dom
What is Kastor Gen?
Kastor Gen generates type-safe domain interfaces from your SHACL/JSON-LD ontologies, eliminating manual interface writing and ensuring your code always matches your data model.
Kastor Gen is a Kotlin library that provides:
🎯 Pure Domain Interfaces
Create clean, RDF-free domain interfaces that represent your business concepts:
// Using full IRIs
@Rdf(iri = "http://www.w3.org/ns/dcat#Catalog")
interface Catalog {
@Rdf(iri = "http://purl.org/dc/terms/title")
val title: String
@Rdf(iri = "http://purl.org/dc/terms/description")
val description: String
@Rdf(iri = "http://www.w3.org/ns/dcat#dataset")
val dataset: List<Dataset>
}
// Using QNames (recommended): `@file:Rdf(prefixes = …)` then `@Rdf(iri = "prefix:local")`
@file:Rdf(
prefixes = [
Prefix("dcat", "http://www.w3.org/ns/dcat#"),
Prefix("dcterms", "http://purl.org/dc/terms/")
]
)
package com.example
import com.geoknoesis.kastor.gen.annotations.Prefix
import com.geoknoesis.kastor.gen.annotations.Rdf
@Rdf(iri = "dcat:Catalog")
interface Catalog {
@Rdf(iri = "dcterms:title")
val title: String
@Rdf(iri = "dcterms:description")
val description: String
@Rdf(iri = "dcat:dataset")
val dataset: List<Dataset>
}
🔄 Automatic Materialization
Convert RDF nodes to domain objects seamlessly:
val catalog: Catalog = graph.materialize(iri("https://data.example.org/catalog"))
// Pure domain usage
println("Title: ${catalog.title}")
println("Dataset count: ${catalog.dataset.size}")
🚀 RDF Side-Channel Access
Access RDF power when needed without polluting domain interfaces:
// Side-channel access
val rdfHandle = catalog.asRdf()
val extras = rdfHandle.extras
// Access unmapped properties
val altLabels = extras.strings(SKOS.altLabel)
val allPredicates = extras.predicates()
// Validation
rdfHandle.validateOrThrow()
Ontology-driven code generation
Annotate a small generator class (or use @file:Rdf on the package file) with @Rdf(shacl = …, context = …). Paths are under src/main/resources. KSP emits the selected artifacts into the target package.
Kastor Gen supports two generation modes that can be used independently or together:
| Mode | Generated artifacts | Loading | Equality |
|---|---|---|---|
| Interface + Wrapper | Domain interface + *Wrapper class |
Lazy (property delegates) | Identity |
| Data class | *Record data class + *RecordFactory object |
Eager (at factory time) | Structural (data class) |
Interface + Wrapper mode (default — live, lazy-loaded view):
@Rdf(
shacl = "ontologies/dcat.shacl.ttl",
context = "ontologies/dcat.context.jsonld",
packageName = "com.example.generated",
generateInterfaces = true,
generateWrappers = true,
)
class OntologyGenerator
Data class mode (immutable snapshot — no RDF types in the class body):
@Rdf(
shacl = "ontologies/dcat.shacl.ttl",
context = "ontologies/dcat.context.jsonld",
packageName = "com.example.generated",
generateDataClass = true,
dataClassSuffix = "Record", // CatalogRecord, DatasetRecord, …
nestedMode = NestedMode.DATA_CLASS, // recursive snapshots, or INTERFACE / IRI_ONLY
)
class OntologyGenerator
Both modes register in OntoMapper so graph.materialize<CatalogRecord>(node) works the same as for wrappers.
Key Features
✅ Type Safety
- Compile-time validation of property types
- Automatic mapping from SHACL datatypes to Kotlin types
- Cardinality constraints enforced at the type level
- Benefit: 100% type safety, zero runtime type errors
✅ Automatic Generation
- Generate interfaces from SHACL shapes
- Generate wrappers from JSON-LD context
- Single source of truth (ontology files)
- Benefit: 90% less manual code, 100% consistency
✅ Performance
- Lazy evaluation of properties
- Efficient RDF graph traversal
- Minimal memory footprint
- Benefit: No performance overhead vs manual code
✅ Flexibility
- Pure domain interfaces with optional RDF access
- Support for complex object relationships
- Extensible validation system
- Benefit: Clean business code, RDF power when needed
✅ Standards Compliance
- Full SHACL (Shapes Constraint Language) support
- JSON-LD context integration
- RDF 1.1 specification compliance
- Benefit: Industry-standard ontologies work out of the box
Quick Start
⚡ Quick Benefit: Instead of spending 1-2 hours writing a domain interface manually, Kastor Gen generates it in 2 minutes from your SHACL ontology. See how →
1. Add Dependencies
Kastor Gen is not yet published to Maven Central or the Gradle Plugin Portal. Requires JDK 21+.
Inside the Kastor build (or a composite build that includes it):
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.google.devtools.ksp")
}
dependencies {
implementation(project(":kastor-gen:runtime"))
ksp(project(":kastor-gen:processor"))
}
Generated interfaces and data classes carry jakarta.validation annotations (@NotNull, @NotEmpty, @Size)
by default; kastor-gen-runtime brings jakarta.validation-api onto your compile class path, so both
mode examples above compile as they are. Set validationAnnotations = ValidationAnnotations.NONE to omit the
annotations, or JAVAX and add javax.validation:validation-api (see the
annotation reference).
From another project, first run
./gradlew :rdf:core:publishToMavenLocal :kastor-gen:runtime:publishToMavenLocal :kastor-gen:processor:publishToMavenLocal
in a Kastor checkout, add mavenLocal() to your repositories, and depend on
com.geoknoesis.kastor:kastor-gen-runtime / ksp("com.geoknoesis.kastor:kastor-gen-processor") with the
Kastor build’s project version. See Getting Started.
Try the working examples in the repository:
./gradlew :examples:hello-codegen:run
./gradlew :examples:dcat-us:runGeneratedExample
2. Define Domain Interface
@Rdf(iri = "http://www.w3.org/ns/dcat#Catalog")
interface Catalog {
@Rdf(iri = "http://purl.org/dc/terms/title")
val title: String
@Rdf(iri = "http://www.w3.org/ns/dcat#dataset")
val dataset: List<Dataset>
}
3. Use Generated Wrappers
// KSP generates CatalogWrapper automatically
val catalog: Catalog = graph.materialize(iri("https://data.example.org/catalog"))
// Pure domain usage - no RDF dependencies in business code
println("Title: ${catalog.title}")
println("Datasets: ${catalog.dataset.size}")
🛡️ Benefit: Type-safe access with compile-time validation. The
titleproperty is guaranteed to exist (from SHACL minCount constraint).
4. Access RDF Side-Channel
// When you need RDF power
val rdfHandle = catalog.asRdf()
val extras = rdfHandle.extras
// Access unmapped properties
val altLabels = extras.strings(SKOS.altLabel)
val allPredicates = extras.predicates()
// Validate against SHACL
rdfHandle.validateOrThrow()
🔌 Benefit: Clean separation - pure domain interfaces for business logic, RDF side-channel when you need advanced features. Best of both worlds.
Architecture Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Domain │ │ Kastor Gen │ │ RDF Graph │
│ Interfaces │◄──►│ Runtime │◄──►│ (Kastor) │
│ │ │ │ │ │
│ • Pure Kotlin │ │ • Materialization│ │ • Triples │
│ • No RDF deps │ │ • Type mapping │ │ • SPARQL │
│ • Business logic│ │ • Validation │ │ • Serialization │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Generated │ │ KSP Processor │ │ SHACL/JSON-LD │
│ Wrappers │◄───│ │◄───│ Ontology │
│ │ │ • Code generation│ │ Files │
│ • RDF-backed │ │ • Type inference │ │ │
│ • Lazy loading │ │ • Validation │ │ • Shapes │
│ • Side-channel │ │ • Registry │ │ • Context │
└─────────────────┘ └──────────────────┘ └─────────────────┘
Use Cases
📊 Data Catalogs
- DCAT (Data Catalog Vocabulary) compliance
- Government open data portals
- Enterprise data governance
🏢 Enterprise Integration
- RDF-based data lakes
- Semantic web applications
- Knowledge graphs
🔬 Research & Academia
- Scientific data management
- Research data repositories
- Ontology-driven applications
🌐 Web Applications
- Linked data publishing
- Semantic search
- Content management systems
Getting Help
📚 Documentation
💬 Community
- GitHub Issues for bug reports
- GitHub Discussions for questions
- Stack Overflow with
kastor-gentag
🛠️ Examples
- Examples documentation — walkthroughs and patterns in this site
- DCAT-US (Gradle module on GitHub) — runnable
:examples:dcat-uswith SHACL + JSON-LD - Hello Codegen — minimal KSP + ontology inputs
- Hello World — RDF-only starter
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
git clone https://github.com/geoknoesis/kastor.git
cd kastor
./gradlew build
Running Tests
./gradlew test
Building Documentation
./gradlew dokkaHtml
License
Kastor Gen is licensed under the Apache License 2.0.
Acknowledgments
- Built on top of Kastor RDF
- Inspired by Apache Jena
- Compatible with RDF4J
- Supports SHACL validation
Ready to get started?
- Getting Started Guide - Step-by-step tutorial
- Benefits & Value - Why use Kastor Gen?
- Manual vs Generated - See the difference