Lazyval: Compile-Time Code Generator for Domain Primitives in Java & Kotlin

Motivation

Good domain modeling means using dedicated types instead of raw primitives. A Quantity, an Isbn, or an EmailAddress each carry their own invariants and make invalid states unrepresentable. The book Secure by Design calls these "Domain Primitives" — the idea was also inspired by Scala’s AnyVal.

  • Java

  • Kotlin

A simple domain primitive
@LazyValue()
public record Quantity(int value) {

    public Quantity {
        if(value <= 0){
            throw new IllegalArgumentException("Quantity must be greater than 0");
        }
    }
}
A simple domain primitive
@LazyValue
data class Quantity(val value: Int){
    init {
        require(value > 0) { "Quantity must be greater than 0" }
    }
}

The Problem

In practice, every domain primitive you introduce comes with a tax: you need to write a JPA AttributeConverter so the ORM can persist it, a Jackson de/serializer so it travels correctly/efficient over the network, a MapStruct mapping so it converts between layers, and so on. This boilerplate adds up quickly and discourages teams from modeling their domain properly — they fall back to raw String and int fields instead.

How Lazyval Solves It

Lazyval is a compile-time code generator that eliminates this boilerplate — with first-class support for both Java (annotation processor) and Kotlin (KSP2). Annotate your type with @LazyValue (or configure shared types), and Lazyval generates the necessary integration code during compilation — no runtime dependency, no marker interfaces, no glue code.

It is also smart about it:

  • Classpath detection — only generates code for frameworks that are actually on your classpath. Add JPA? Converters appear. Remove it? They’re gone.

  • Convention-free — factory methods can be named arbitrarily; Lazyval infers the right one from the type signature.

  • Proper feedback — warnings and errors are reported during compilation, not at runtime.

In a possible future where Java value types are fully supported by the ecosystem (meaning you can use a value type in a JPA entity without hassle), this project may no longer be needed.

Supported Generators

See Generators Overview for how to plug in user-supplied converters where the framework requires application-specific decisions (e.g. timezone for LocalDate ↔ Date on MongoDB).

For custom generators, see Lazyval’s SPI.

Global Configuration Options

Property Description

lazyval.generators.basePackage

When this option is set with the applications' base package, the generators use sensible defaults where the generated artifacts should be located (following hexagonal architecture guidelines). See specific generator about defaults and how to override. In case no basePackage nor generator-specific override is provided, the package of the first domain-primitive is used as fallback (custom generators might choose a different strategy).

lazyval.generators.disable

Comma separated List of Generator-IDs that should not generate output despite being found on the classpath. Active Generator-IDs are printed to the console during compilation.

External value types from other modules are declared via the @LazyvalConfiguration annotation rather than a processor option. See Multi-Module Support.

For generator-specific options, see the Generators section.

License

This project is licensed under the [Apache License 2.0](LICENSE). You are free to use, modify, and distribute it under the terms of that license.

Cyber Resilience Act (CRA) Notice

Important Notice regarding the EU Cyber Resilience Act (CRA): This project is a developer tool and does not constitute a "product with digital elements" as defined under the CRA. It is not executed in production environments, nor does it process data during runtime.

The responsibility for the security, compliance, and correctness of the generated code lies entirely with the user. If the generated clients are used in safety-critical or regulated domains, users must perform their own security and compliance assessments.

Disclaimer of Liability

This project provides templates and helper scripts on an "as-is" basis, without any warranties.

The maintainers do not accept any liability for:

  • the security or correctness of generated code,

  • suitability for use in any specific technical or regulatory context.

Use of this project is at your own risk. Always validate generated code in accordance with your organization’s quality and security standards.