Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ A zero-reflection Java annotation processor that generates fluent, type-safe bui
## Table of Contents
- [What is Simple Builders?](#what-is-simple-builders)
- [How Simple Builders compares](#how-simple-builders-compares)
- [Doing what other builders advertise — the Simple Builders way](#doing-what-other-builders-advertise-the-simple-builders-way)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Usage](#basic-usage)
- [Validation Annotations](#validation-annotations)
- [Required Fields and Null-Safety](#required-fields-and-null-safety)
- [Conditional Builder Logic](#conditional-builder-logic)
- [Collections and Nested Objects](#collections-and-nested-objects)
- [Collection Immutability](#collection-immutability)
- [With Interface Pattern](#with-interface-pattern)
- [Builder Configuration](#builder-configuration)
- [Compiler Arguments](#compiler-arguments)
Expand All @@ -47,6 +50,16 @@ Simple Builders generates fluent, type-safe builders for your **existing** class

Use Simple Builders when you want fluent, type-safe builders for the classes and records you already have, generated as plain readable source, with no bytecode manipulation and no IDE plugin — and no lock-in: because the builders are ordinary generated Java, you can drop the dependency at any time by copying the generated builder classes into your own sources, and they keep working.

### Doing what other builders advertise — the Simple Builders way

- **Required fields:** Primitive fields and fields annotated with an annotation named `NotNull` or `NonNull` are non-nullable; constructor parameters are builder inputs. `build()` enforces the required/non-null contract with `IllegalStateException` ([configuration details](#required-fields-and-null-safety)).
- **Copy / `with` / `toBuilder`:** The generated `With` interface provides `instance.with(b -> ...)` for copy-and-modify and `instance.with()` for a builder pre-populated from the instance ([`generateWithInterface`](docs/CONFIGURATION.md#generatewithinterface)).
- **Collection immutability:** The target type owns the collection contract. Simple Builders passes through what the type stores; use defensive copying such as `List.copyOf(...)` in the type when the result must be immutable ([configuration details](#collection-immutability)).
- **Incremental / singular collection API:** `add2X` helpers, `ArrayList`/`HashSet`/`HashMap` collection builders, and varargs helpers cover incremental collection construction ([collection helper options](docs/CONFIGURATION.md#collection-helpers)).
- **Inheritance:** Inherited setters are discovered, and constructors exposed by the annotated subclass are used. A final superclass field not exposed by that constructor is intentionally not bypassed ([configuration options](docs/CONFIGURATION.md#configuration-options)).

Value semantics (`equals`, `hashCode`, `toString`) and generating brand-new immutable value types are deliberate out-of-scope paradigm choices, not missing builder features.

## Features

- **Low Runtime Dependencies**: The generated code has only dependencies to a core dependency for CollectionBuilders and to Apache.CommonLang3
Expand Down Expand Up @@ -179,6 +192,35 @@ User user = UserBuilder.create()

This ensures validation frameworks work seamlessly with builder-generated objects.

#### Required Fields and Null-Safety

Simple Builders treats a field as non-nullable when its type is primitive or its parameter carries
an annotation whose simple name is `NotNull` or `NonNull`, regardless of the annotation package.
The name is matched without requiring a particular validation framework.

For non-nullable constructor fields, `build()` requires the builder value to be set and non-null.
For non-nullable setter fields, the field remains optional, but a value supplied to the builder must
be non-null. Violations throw `IllegalStateException`.

For example, both the primitive `price` and the `@NotNull` `name` below are required — omitting
either makes `build()` fail:

```java
@SimpleBuilder
public record Product(@NotNull String name, double price) {}

ProductBuilder.create()
.name("Keyboard")
.build(); // throws IllegalStateException: price must be set

ProductBuilder.create()
.price(99.0)
.build(); // throws IllegalStateException: name must be set
```

Comment thread
AndreasIgel marked this conversation as resolved.
The `NotNull`/`NonNull` simple-name check is framework-agnostic; use the annotation type already
used by your project.

#### Conditional Builder Logic

Apply builder modifications conditionally using the `conditional()` method:
Expand Down Expand Up @@ -255,6 +297,25 @@ Project project = ProjectBuilder.create()
.build();
```

#### Collection Immutability

Immutability of collections in the built object is the target type's responsibility. Simple
Builders stores exactly what the target type stores and does not silently wrap collections, so the
target type's collection contract remains intact. For a record, enforce that contract in its
canonical or compact constructor:

```java
@SimpleBuilder
public record Basket(List<String> items) {
public Basket {
items = List.copyOf(items);
}
}
```

The builder passes its collection value to `Basket`; the record makes the stored collection
unmodifiable.


### With Interface Pattern

Expand Down