Java
Usage
First, add the annotation and processor dependencies to your favorite build-tool. See below for Setup.
Mark your domain-primitive with the @LazyValue annotation and the processor will figure out the rest.
@LazyValue()
public record Quantity(int value) {
public Quantity {
if(value <= 0){
throw new IllegalArgumentException("Quantity must be greater than 0");
}
}
}
@LazyValue
public final class Isbn {
private final String value;
private Isbn(String value){
Objects.requireNonNull(value);
this.value = value;
}
public String value(){
return value;
}
// will be used by the annotation processor (factory methods have higher precedence)
public static Isbn parse(String value) throws IllegalArgumentException {
Objects.requireNonNull(value, "ISBN cannot be null");
String cleanValue = value.replaceAll("[-\\s]", "");
if (cleanValue.length() == 10) {
validateIsbn10(cleanValue);
} else if (cleanValue.length() == 13) {
validateIsbn13(cleanValue);
} else {
throw new IllegalArgumentException("Invalid ISBN length. Must be 10 or 13 digits (excluding hyphens)");
}
return new Isbn(value);
}
// ... validation, equals, hashCode, toString
@LazyValue
public final class Birthdate {
// ISO-8601 string, the canonical storage form
private final String value;
// derived state computed from `value` — excluded from validation by `transient`
private final transient LocalDate parsed;
private Birthdate(String value) {
this.value = value;
this.parsed = LocalDate.parse(value);
}
public String value() {
return value;
}
public LocalDate asLocalDate() {
return parsed;
}
public static Birthdate of(String isoDate) {
Objects.requireNonNull(isoDate, "Birthdate cannot be null");
return new Birthdate(isoDate);
}
}
A value type may carry derived fields that are computed from the stored payload (here parsed from value).
Lazyval validates that exactly one non-transient instance field is the stored payload, so any derived field must
be marked transient to keep it out of the validation. Additional constructors, accessors, or convenience
factories with other signatures are allowed and ignored by the processor.
Setup
The following snippets only contain the relevant parts to set up and configure Lazyval.
The jakarta-ee dependency can be replaced by any that ships jakarta-persistence.
Keep in mind, Lazyval will only activate generators whose required dependencies are
available.
| These samples show the full setup, using the smallest dependencies. In case a feature is not needed, just remove the dependency. |
-
Maven
-
Maven 4
-
Gradle
-
Mill
<properties>
<version.lazyval>0.3.2</version.lazyval>
<version.mapstruct>1.6.3</version.mapstruct>
<version.jakarta-ee>11.0.0</version.jakarta-ee>
<version.jackson>3.1.0</version.jackson>
<version.cassandra-driver>4.19.2</version.cassandra-driver>
</properties>
<dependencies>
<dependency>
<groupId>com.qualityminds.lazyval</groupId>
<artifactId>lazyval</artifactId>
<version>${version.lazyval}</version>
<optional>true</optional> (1)
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${version.mapstruct}</version>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>${version.jakarta-ee}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>tools.jackson.core</groupId>-->
<!-- <artifactId>jackson-databind</artifactId>-->
<!-- <version>${version.jackson}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-query-builder</artifactId>
<version>${version.cassandra-driver}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
<proc>full</proc>
<annotationProcessorPaths>
<path>
<groupId>com.qualityminds.lazyval</groupId>
<artifactId>lazyval-processor</artifactId>
<version>${version.lazyval}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${version.mapstruct}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.unmappedTargetPolicy=ERROR</arg>
<arg>-Alazyval.generators.basePackage=test</arg>
<arg>-Alazyval.mapstruct.package=test.custom</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
| 1 | mark the dependency as optional to avoid runtime-dependency |
Maven 4 changes the way the annotation-processor-classpath is set.
<properties>
<version.lazyval>0.3.2</version.lazyval>
<version.mapstruct>1.6.3</version.mapstruct>
<version.jakarta-ee>11.0.0</version.jakarta-ee>
<version.jackson>3.1.0</version.jackson>
</properties>
<dependencies>
<dependency>
<groupId>com.qualityminds.lazyval</groupId>
<artifactId>lazyval</artifactId>
<version>${version.lazyval}</version>
<optional>true</optional> (1)
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${version.mapstruct}</version>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>${version.jakarta-ee}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${version.jackson}</version>
</dependency>
<!-- new way to define processors in Maven 4 (parameters remain in maven-compiler-plugin) -->
<dependency>
<groupId>com.qualityminds.lazyval</groupId>
<artifactId>lazyval-processor</artifactId>
<version>${version.lazyval}</version>
<type>classpath-processor</type> (2)
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${version.mapstruct}</version>
<type>classpath-processor</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>4.0.0-beta-3</version>
<configuration>
<release>17</release>
<proc>full</proc>
<compilerArgs>
<arg>-Amapstruct.unmappedTargetPolicy=ERROR</arg>
<arg>-Alazyval.generators.basePackage=test</arg>
<arg>-Alazyval.mapstruct.package=test.custom</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
| 1 | mark the dependency as optional to avoid runtime-dependency |
| 2 | annotation-processors have their own scope now |
dependencies {
compileOnly("com.qualityminds.lazyval:lazyval:$versionLazyval")
implementation("org.mapstruct:mapstruct:1.6.3")
compileOnly("jakarta.platform:jakarta.jakartaee-api:11.0.0")
implementation("tools.jackson.core:jackson-databind:3.1.0")
annotationProcessor("com.qualityminds.lazyval:lazyval-processor:$versionLazyval")
annotationProcessor("org.mapstruct:mapstruct-processor:1.6.3")
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.apply{
add("-Amapstruct.unmappedTargetPolicy=ERROR")
add("-Alazyval.generators.basePackage=test")
add("-Alazyval.mapstruct.package=test.custom")
}
}
override def mvnDeps: T[Seq[Dep]] = Seq(
mvn"org.mapstruct:mapstruct:${Version.mapstruct}",
)
override def compileMvnDeps: T[Seq[Dep]] = Seq(
mvn"com.qualityminds.lazyval:lazyval:${Version.lazyval}",
mvn"jakarta.persistence:jakarta.persistence-api:${Version.jpa}",
mvn"tools.jackson.core:jackson-databind:${Version.jackson}"
)
override def annotationProcessorsMvnDeps = Seq(
mvn"com.qualityminds.lazyval:lazyval-processor:${Version.lazyval}",
mvn"org.mapstruct:mapstruct-processor:${Version.mapstruct}"
)
def javacOptions = super.javacOptions() ++ Seq(
"-Amapstruct.unmappedTargetPolicy=ERROR",
"-Alazyval.generators.basePackage=test",
"-Alazyval.mapstruct.package=test.custom",
)