Code Generation

kUML ships three code generators out of the box. They share a CodeGenRegistry infrastructure, so authoring a new generator follows the same pattern as a theme or profile — META-INF/services registration, done.

Built-in generators

Generator Output

kotlin

Kotlin data classes for each UmlClass, sealed interfaces for UmlInterface, Kotlin enums for UmlEnumeration. Operations become member functions.

java

Java POJOs, records, or Lombok-annotated classes. Honours JavaEE / JPA stereotypes for @Entity, @Id, @Column, @Transient.

sql

DDL for PostgreSQL (default), MySQL, H2, or SQLite. Topologically sorted CREATE TABLE statements with FK block at the end. Honours Entity and Id stereotypes for table names and primary keys.

Generating Kotlin

kuml generate --input model.kuml.kts \
    --plugin kotlin \
    --package com.example.domain \
    --output gen/

Output structure:

gen/
└── com/
    └── example/
        └── domain/
            ├── Order.kt
            ├── OrderItem.kt
            └── OrderStatus.kt

The Kotlin generator produces idiomatic data classes with no framework dependencies. Operations become method stubs marked TODO() — kUML generates signatures, not implementations. That’s a deliberate choice: model the contract, write the body in Kotlin.

Generating Java

kuml generate --input model.kuml.kts \
    --plugin java \
    --package com.example.domain \
    --options java-style=records \
    --output gen/

java-style options:

  • pojo (default) — classic class with private fields and getters/setters

  • records — Java 16+ record types

  • lombok@Data-annotated POJO (requires the Lombok dependency)

The Java generator inspects applied JavaEE stereotypes:

classOf("Order") {
    stereotype("Entity", "tableName" to "orders")
    attribute("id", "UUID") { stereotype("Id") }
    attribute("notes", "String") { stereotype("Transient") }
}

produces:

@jakarta.persistence.Entity(name = "orders")
public class Order {
    @jakarta.persistence.Id
    private UUID id;

    @jakarta.persistence.Transient
    private String notes;

    // getters/setters omitted
}

JavaEE annotations are emitted fully qualified — that side-steps import collisions and makes the output self-documenting. Lombok-mode is the exception (mainstream pattern, short forms expected).

Generating SQL DDL

kuml generate --input model.kuml.kts \
    --plugin sql \
    --options sql-dialect=postgres,sql-drop=true \
    --output gen/

sql-dialect options: postgres (default), mysql, h2, sqlite. sql-drop=true prepends DROP TABLE IF EXISTS … statements (handy for migrations).

The output is a single schema.sql with:

  1. Optional DROP TABLE block

  2. Enum types emitted as VARCHAR with a CHECK constraint on all dialects

  3. CREATE TABLE statements, topologically sorted so referenced tables exist before referrers

  4. ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY … block at the end (avoids circular FK problems during deployment)

Multiplicity translates to constraints:

UML multiplicity SQL

(1, 1)

NOT NULL

(0, 1)

NULL

(1, ) or (, *) (M:N)

Real junction table with a composite primary key (emitted via the ERM path)

Stereotype-driven mapping:

  • Entity{tableName="users"}CREATE TABLE users (…)

  • IdPRIMARY KEY

  • Column{name="created_at"} → renames the column

Under the hood, --plugin sql runs on an ERM model, not directly on the UML model — a UML script is first transformed to ERM (UML → ERM), then ErmSqlEmitter turns the ERM model into DDL (ERM → SQL). This intermediate step is transparent for the UML-direct path above, but it is also directly accessible: author an ermModel(…​) { … } script yourself and generate from it with the same --plugin sql, or with --plugin exposed for Kotlin Exposed tables. See ERM DSL for the full model, all four rendering notations, and the UML→ERM transform.

Generating from Gradle

The Gradle plugin’s kumlGenerate task takes the same options through the extension:

kuml {
    generator.set("java")
    generatePackage.set("com.example.domain")
    generateOptions.put("java-style", "records")
}

kumlGenerate writes to ${buildDir}/kuml/generated/ by default. Configure outputDir.dir("generated") to override.

C++ and C# code generation

kUML ships two optional codegen plugins for native languages:

Plugin id Module Output

cpp

kuml-gen-cpp

ISO C++ 17 header (.hpp) + implementation stub (.cpp) per class

csharp

kuml-gen-csharp

C# 10 .cs file per class, auto-properties, nullable annotations

kuml generate model.kuml.kts \
    --plugin cpp \
    --namespace com::example::domain \
    --output gen/

kuml generate model.kuml.kts \
    --plugin csharp \
    --namespace Example.Domain \
    --output gen/

The C++ generator produces one .hpp / .cpp pair per UmlClass. Forward declarations handle circular includes; enum class maps to UmlEnumeration; virtual methods with = 0 stub operations on abstract classes. The C# generator emits idiomatic C# 10: record for value objects (no mutable attributes), class otherwise; List<T> for 1..* multiplicities; nullable reference types (T?) for optional attributes.

C++ and C# reverse engineering

kuml reverse reconstructs a UML class diagram from existing C++ or C# source code:

# C# — reads all *.cs files in the directory tree
kuml reverse --format csharp ./src/ --output model.kuml.kts

# C++ — reads all *.hpp / *.h / *.cpp files
kuml reverse --format cpp ./include/ --output model.kuml.kts

Both plugins ship as example plugins under kuml-plugin-examples/:

Plugin Module

plugin-reverse-csharp

kuml-plugin-examples/plugin-reverse-csharp

plugin-reverse-cpp

kuml-plugin-examples/plugin-reverse-cpp

The C# reverse engine (CsharpReverseEngine) is a handwritten four-phase structural parser — no ANTLR dependency. It handles:

  • File-scoped namespaces (namespace X.Y;) and block namespaces.

  • class, abstract class, sealed class, static class, interface, struct, record, enum.

  • Auto-properties (T Name { get; set; }), methods, fields, readonly fields.

  • Generic types: List<T>, IEnumerable<T>, T[], Dictionary<K,V>, HashSet<T>.

  • Base list classification: exact known-interface set first, then I-prefix heuristic.

Safety limits: 10 MB per file, 2000 files per run, symlink-escape guard.

The C++ reverse engine uses the same structural approach, parsing headers for class and struct declarations, method signatures, and inheritance lists.

Register the plugins via META-INF/services in the standard plugin loader way, or pass --plugin-jar plugin-reverse-csharp.jar on the CLI.

Authoring a custom generator

Implement KumlCodeGenerator and provide a KumlCodeGeneratorProvider:

class TypeScriptCodeGenerator : KumlCodeGenerator {
    override fun generate(
        diagram: KumlDiagram,
        outputDir: File,
        options: Map<String, String>,
    ): List<File> {
        // emit .ts files, return the list of written files
    }
}

class TypeScriptCodeGeneratorProvider : KumlCodeGeneratorProvider {
    override val name: String = "typescript"
    override fun create(): KumlCodeGenerator = TypeScriptCodeGenerator()
}

Register via META-INF/services/dev.kuml.codegen.api.KumlCodeGeneratorProvider. CLI/Gradle pick it up via --plugin typescript / generator.set("typescript").

The KumlCodeGenerator API is intentionally minimal — diagram + outputDir + options in, list of written files out. Generators decide their own directory layout.