# v0.4.10 (30 August 2026) {#v0410-30-august-2026}

[v0.4.10 on GitHub](https://github.com/higher-kinded-j/higher-kinded-j/releases/tag/v0.4.10)

This release gives the mapper a stock vocabulary and takes every generating annotation through a generics pass: each one now emits source the consuming build compiles under `-Werror`, or explains at the declaration what it needs instead. The book gains a **Mapping at the Boundary** chapter with a law-checked capstone, and the house style it introduced now runs through the Optics and Resilience chapters.

**Mapping at the Boundary** ([#670](https://github.com/higher-kinded-j/higher-kinded-j/issues/670), [#671](https://github.com/higher-kinded-j/higher-kinded-j/issues/671), [#672](https://github.com/higher-kinded-j/higher-kinded-j/issues/672), [#690](https://github.com/higher-kinded-j/higher-kinded-j/issues/690), [#752](https://github.com/higher-kinded-j/higher-kinded-j/issues/752))

- **A chapter of its own**: the record-mapping material becomes [Mapping at the Boundary](../mapping/ch_intro.md), covering basics, codecs, structure, tiers, beans and sparse PATCH, generics, merge and envelopes, injection and testing. It opens with the response a client sees and closes with a compiled, law-checked capstone, [One 422, Every Bad Field](../mapping/capstone.md). The old `optics/record_mapping.html` URL redirects. Two hands-on journeys sit beside it: [Batching & Coupled Updates](../tutorials/optics/batching_journey.md) and [Boundary Mapping](../tutorials/optics/boundary_mapping_journey.md).
- **Stock codec vocabulary**: `StandardCodecs` ships one `ValidatedPrism` per standard conversion family (`uuid`, `uri`, `localDate`, `instant`, `offsetDateTime`, `enumByName`, `bigDecimal`, the numeric-from-string trio, `booleanStrict`, `currency`, `locale`), so a typical DTO boundary maps with no hand-written leaves. Every codec accepts exactly the canonical form it renders, and every failure is a located `FieldError` that feeds the 422 leg unchanged. See [Standard codecs](../mapping/codecs.md#standard-codecs).
- **`ValidatedPrism.canonical(message, parse, render)`** (plus a `FieldError` overload) wraps a throwing parser and a total render with the build-parse section law guarded per value: an accepted source must render back to itself, so a custom-canon codec is lawful without hand-writing the check. See [Laws](../optics/validated_prism.md#laws).
- **Records of any width**: `parse`, the validated `patch` and fallible `@GenerateMerge` legs assemble records beyond 16 components through chunked `Validated.fields()` ladders, with the same located labels and declaration-order accumulation, law-checked at width. The only remaining bound is the JVM's constructor-slot limit on the record itself; the hand-written `fields()` ladder keeps its 16-field arity. See [Diagnostics and limits](../mapping/testing.md#diagnostics-and-limits).
- **One leaf vocabulary across tiers**: the sparse `UpdateSpec` tier lifts element leaves over present `List`, `Optional` and `Map` properties exactly as the dense tiers do, every failing element located (`phones.1`), so one mix-in vocabulary serves a spec and its PATCH sibling. See [Sparse PATCH write-back](../mapping/beans_patch.md#sparse-patch-write-back-updatespec).
- **Generic mix-ins**: a spec may extend a mix-in declaring type parameters, so `extends Emails<EmailAddress>` contributes `ValidatedPrism<String, EmailAddress>`, read under the spec's instantiation. A generic ancestor reached through a raw `extends` clause is still refused, naming the interface whose clause is raw. See [Generic Specs](../mapping/generics.md#generic-mix-ins).

_Behaviour changes:_ a present same-typed identity container in a sparse PATCH now carries the dense tiers' null scan (`tags.1: must not be null` rather than a null reaching the domain); an `Optional`-typed PATCH property with a differing element type, previously rejected, patches through its element leaf; a spec extending a generic mix-in, previously refused, generates.

**Generated optics for generic types** ([#721](https://github.com/higher-kinded-j/higher-kinded-j/issues/721), [#723](https://github.com/higher-kinded-j/higher-kinded-j/issues/723), [#730](https://github.com/higher-kinded-j/higher-kinded-j/issues/730), [#733](https://github.com/higher-kinded-j/higher-kinded-j/issues/733), [#736](https://github.com/higher-kinded-j/higher-kinded-j/issues/736), [#740](https://github.com/higher-kinded-j/higher-kinded-j/issues/740), [#742](https://github.com/higher-kinded-j/higher-kinded-j/issues/742), [#746](https://github.com/higher-kinded-j/higher-kinded-j/issues/746), [#750](https://github.com/higher-kinded-j/higher-kinded-j/issues/750))

Every processor now reads a member or type under the *instantiation* the spec names rather than the source's own declaration, so a generic spec generates the signature it means, whatever it calls its parameters. Axis tests hold each generating annotation to the consuming build's `-Xlint:unchecked,rawtypes -Werror`.

- **`@ImportOptics` declares the type parameters its signatures name**, in the spec's declaration order with bounds carried along: `BoxOpticsSpec<U> extends OpticsSpec<Box<U>>` generates `<U>`, and a concrete instantiation declares none. An optic method declaring parameters of its own is refused. `@GeneratePrisms` on a generic sealed hierarchy emits parameterised prisms. See [Generic spec interfaces](../optics/optics_spec_interfaces.md#generic-spec-interfaces).
- **`@InstanceOf` narrows to what its test can check**: the generated `instanceof` is written under the type arguments the source type pins (`Circle<U>` from `Shape<U>`), an unbounded wildcard elsewhere, and an array target through its component, and the generated methods no longer need `@SuppressWarnings`. A focus asking for more is refused with both remedies, widening to the wildcard or narrowing through `@MatchWhen`. See [Parameterised targets](../optics/optics_spec_interfaces.md#parameterised-targets).
- **`@GenerateIsos` answers a generic declaration at the declaration**: an iso naming a type variable, an instance method, one taking arguments, one the generated package cannot see, or a return type that is not a two-argument `Iso` is each refused where it is written, with the remedy, rather than reported by javac inside the generated file. See [Compiler Errors](../optics/compiler_errors.md#generateisos-the-iso-returned-by-x-names-a-type-variable).
- **`@ViaCopyAndSet(copyConstructor = ...)` selects the constructor it names**: the attribute resolves to the supertype of `S` it stands for and is emitted as a cast on the argument, `new Config((BaseConfig) source)`, which is what picks between overloaded copy constructors. A name that does not resolve, is not a supertype, cannot be seen, or no constructor accepts is refused at the declaration. See [Copy Strategies](../optics/copy_strategies.md#viacopyandset-legacy-types-with-a-copy-constructor-and-setters).
- **Type-use annotations reach generated source**: a generated signature now carries the annotations the author wrote at every depth, so `@Nullable String note` stays `@Nullable` in the generated lens, which matters inside a consumer's `@NullMarked` package where a bare type means non-null. One exception by design: a focus widened through `.nullable()` drops the nullness the widening consumed, so `@Nullable String label` is `AffinePath<Box, String>` while `List<@Nullable String> tags` is `TraversalPath<Box, @Nullable String>`.

_Behaviour changes, source-breaking in narrow places:_ a generated `@ImportOptics` method's arity can fall (`OpticsSpec<Pair<A, String>>` now generates `<A>`, not `<A, B>`), so a call supplying explicit type witnesses stops compiling; drop them, the inferred result is unchanged. An `@InstanceOf` focus naming a type argument the source does not pin (`Prism<Shape, Circle<T>>` on `OpticsSpec<Shape>`), and a target that is a parameterised member of a generic type, are refused. A `copyConstructor` name that resolves now emits a cast, which can select a different constructor than ran in 0.4.9; check overloaded types with `LensLaws`. Generated signatures that now carry `@Nullable` can change what a nullness checker reports in the consuming build.

**The Focus DSL gives one answer** ([#711](https://github.com/higher-kinded-j/higher-kinded-j/issues/711), [#718](https://github.com/higher-kinded-j/higher-kinded-j/issues/718), [#719](https://github.com/higher-kinded-j/higher-kinded-j/issues/719), [#725](https://github.com/higher-kinded-j/higher-kinded-j/issues/725), [#756](https://github.com/higher-kinded-j/higher-kinded-j/issues/756))

- **One widening analysis**: `ShapesFocus.tags()` and `OuterFocus.shapes().tags()` come from one declaration and now report one path type. A navigation method composes the static Focus method for the field it reaches, so the two cannot drift, and a navigated path carries its field-name segments for `pathString()`. See [Path widening](../optics/focus_navigation.md#path-widening).
- **`Set` and `Collection` components widen through the `Each` that rebuilds them**: a `Set` through `EachInstances.setEach()`, a `Collection` through the new `EachInstances.collectionEach()`, and `@GenerateTraversals` gains a `Collection` generator. Every route to either, whether Focus, `@GenerateTraversals`, `@ImportOptics` or `@ThroughField`, bottoms out in one rebuild policy in `Traversals`: source iteration order preserved, nulls carried through, an unmodifiable set for a set source and an unmodifiable list for anything else. See [Supported container types](../optics/focus_containers.md#supported-container-types) and [Collection Components](../optics/traversals.md#collection-components).
- **`@Nullable` detection reaches every recognised annotation**: JSpecify's (`TYPE_USE`), JetBrains', AndroidX's and SpotBugs' `@Nullable` now widen to an `AffinePath` alongside JSR-305's and Jakarta's, and a container decides its own widening before `@Nullable` is consulted. See [Focus DSL Reference](../optics/focus_reference.md).
- **A container the widening cannot be written for is explained at the declaration**: an SPI container with a raw or wildcard type argument (`Either<String, ? extends Leaf>`, `Set<?>`) has no ground instantiation to infer its optic instance from, so the diagnostic names the component and the concrete alternative, and the generated file still compiles, leaving one message rather than two. See [Compiler Errors](../optics/compiler_errors.md#generatefocus-record-component-xy-has-a-wildcard-type-argument-in-set-extends-t).
- **`@GenerateTraversals` says when it passes over a container**: a component that is a `Collection` or `Map` by erasure and reaches no generator (a `Deque`, a `SortedMap`, a raw `List`) draws a what/why/fix **note** where it is declared. A note rather than a warning, because the annotation has no per-component opt-out and a processor warning cannot be suppressed under `-Werror`. See [Compiler Errors](../optics/compiler_errors.md#generatetraversals-no-traversal-was-generated-for-component-xy-of-type-dequet-a-note).

_Behaviour changes, each replacing a path that threw `ClassCastException` on first use:_ a raw or wildcard `Set` or `Collection` component under `@GenerateFocus` is rejected; name the argument, or keep `@GenerateLenses` and `@GenerateTraversals` alone. Three navigator return types move to what the static method reports: a `Collection` *subtype* such as `ArrayList` is a `FocusPath` over the container; an SPI container of non-navigable elements stops at the container until the declaring record sets `widenCollections = true`; a nested `List<Optional<String>>` composes to the leaf, so drop the trailing `.some()`. A component carrying one of the four newly recognised `@Nullable`s generates `AffinePath` where it generated `FocusPath`; read it with `getOptional`. `Traversals.forSet()` and `traverseSet` hand back an **unmodifiable** set (previously a mutable `LinkedHashSet`), and `EachInstances.setEach()` keeps source order across JVM runs.

**Traversals for every component shape** ([#721](https://github.com/higher-kinded-j/higher-kinded-j/issues/721))

- **`@GenerateTraversals` reads a wildcard type argument**: `List<? extends Leaf>` focuses `Leaf`, and `?` or `? super T` focuses `Object`, for `List`, `Map` and every HKJ and third-party container alike. Four more shapes now generate: an array of a type that cannot be created by name (`List<Leaf>[]`, `Leaf[][]`), a primitive array (boxed through, unboxed back), a generic record (`record Holder<T>(List<T> items)` gets `<T>` on the method), and a record naming its own `F`. Every container shape is compiled through the real processor by a sweep that also pins the element type each one focuses. See [Wildcard Element Types](../optics/traversals.md#wildcard-element-types) and [Generic Records](../optics/traversals.md#generic-records).
- **Map traversals are linear**: `Traversals.traverseMapValues` builds the result once rather than copying the map per entry (a 16 000-entry map traverses in 1.3 ms, down from 851 ms), and accepts a null key or a null the function hands back.

**Spec interfaces explain what they cannot generate** ([#712](https://github.com/higher-kinded-j/higher-kinded-j/issues/712), [#723](https://github.com/higher-kinded-j/higher-kinded-j/issues/723), [#755](https://github.com/higher-kinded-j/higher-kinded-j/issues/755))

- **A generated prism's focus is a variant of its source**: `@InstanceOf` and `@MatchWhen` are both held to the rule at the declaration, since a prism built back with identity needs a focus that is itself a source. The book's examples now focus a variant, and `Prism.of` is the route where the value type is the point. See [Spec Interfaces](../optics/optics_spec_interfaces.md).
- **A `default` method on a spec interface is explained**: a method body cannot be read during annotation processing, so the diagnostic names the two alternatives, a `static` method on the interface or a utility class calling the generated statics, rather than generating a stub. See [Compiler Errors](../optics/compiler_errors.md#xopticsspecfoo-is-a-default-method).
- **One error per problem**: a rejected `@InstanceOf` target, an undetectable `@ThroughField` container or an unresolvable `copyConstructor` reports once, without a second "requires a hint annotation" message.

**`@GeneratePathBridge` emits a bridge the consuming build compiles** ([#746](https://github.com/higher-kinded-j/higher-kinded-j/issues/746), [#748](https://github.com/higher-kinded-j/higher-kinded-j/issues/748))

Members are read under the annotated interface's own instantiation and deduplicated across superinterfaces, with `throws` carried, varargs kept, a self-referential bound still inferring, and the bridge class declaring the interface's type parameters, so a generic service interface bridges cleanly under `-Werror`. `IO<T>` methods bridge through `Path.ioPath`, an inherited `@PathVia` is picked up, and the six shapes with no correct rendering are explained at the declaration. See [Compiler Errors](../optics/compiler_errors.md#generatepathbridge-and-pathvia).

_Behaviour changes:_ a varargs delegate's bridge method is varargs, so a bare `null` argument now needs `(String[]) null`; a raw effect return type, a wildcard `Validated` error type, and a `static` or `private` `@PathVia` method are refused; an interface with no `@PathVia` method draws a processor warning.

**Effect handlers** ([#697](https://github.com/higher-kinded-j/higher-kinded-j/issues/697), [#699](https://github.com/higher-kinded-j/higher-kinded-j/issues/699))

- **`@ComposeEffects` generates typed composition support**: `injectX()` returns `Inject<XKind.Witness, …>`, `functor(...)` takes one `Functor` per effect and returns the composed `EitherFFunctor`, and `BoundSet`'s components are each algebra's `Bound`, with no casts and no suppression. See [Composing effects](../effect/effect_handlers.md#composing-effects).
- **`@EffectAlgebra` accepts any name for the result type parameter**, so `RenamedOp<T>` generates as `Op<A>` does, and the three permit shapes it cannot generate for (dropping, pinning or bounding the algebra's parameter) are explained at the declaration. See [Defining effects](../effect/effect_handlers.md#defining-effects).

_Behaviour changes / migration:_ the last effect in a composition is now injected at the depth its arity implies, which corrects the dispatch of programs that reach it through the generated `*Support`. Each `@ComposeEffects` field must be `Class<XOp<?>>` naming an `@EffectAlgebra`; `BoundSet<F>` becomes `BoundSet`; `functor()` and `BoundSet`'s accessors change erasure. A `*Support` is generated in your own build, so recompiling is the whole migration.

**Compile-time checks**

- **HKJ compiles under its own checker**: every module builds with `-Xplugin:HKJChecker` at zero findings. `discarded-effect` now gates on a sealed `Deferred` capability, so `Path.io(() -> 1).peek(log)` (a silent no-op) is reported while `Path.just(1).peek(log)` (already run) is not, and a `requireNonNull` guard that hands the effect back is read as the pass-through it is. The checker skips `@Generated` types, and a single declaration opts out of one check with `@SuppressWarnings("<check-id>")`, or of all of them with `@SuppressWarnings("hkj-checker")`. See [Compile-Time Checks](../tooling/compile_checks.md#opting-out-one-declaration).

**Documentation**

- **The house style**: the mapping chapter established it (the payoff shown before the theory, the common case served before the fine print, mermaid for flows and decisions in a theme-safe palette, "Why this matters" beside the rule it justifies, Key Takeaways on every page), and it now runs through the [Resilience](../resilience/ch_intro.md) chapter and every sub-chapter of [Optics](../optics/ch_intro.md), the optics diagrams included. The [style guide](https://github.com/higher-kinded-j/higher-kinded-j/blob/main/docs/STYLE-GUIDE.md) records the conventions.

---

**Previous:** [v0.4.11](v0_4_11.md)
**Next:** [v0.4.9](v0_4_9.md)
