v0.4.5 (22 May 2026)
A Uniform Instances Facade for Type-Class Lookup
This release introduces Instances, a single static entry point for obtaining any built-in type-class instance, replacing the three inconsistent legacy idioms (a static INSTANCE field, a generic instance() method, or an argument-taking constructor) with one predictable shape discovered by capability through IDE autocomplete. The whole codebase (tests, runnable examples, and the book) is migrated to the one idiom.
- Obtaining Instances: New
org.higherkindedj.hkt.instancespackage with theInstancesfacade and theWitnessestyped-token helper.Instances.monad/applicative/functor(token)are total (every canonical instance is at least aMonad); a single token yields all three by Java subtyping. Phantom-typed witnesses (Either,Reader,Context,State) still infer their type parameter from the assignment target, matchingEitherMonad.<L>instance()behaviour. The facade is a thin static re-export of the existing accessors (not Spring-wired, notPathRegistry/ServiceLoader-backed) so compile-time safety is preserved and no built-in instance can be missing at runtime (#522) - Partial capability lookups:
Instances.monadError,monadZeroandalternativefor canonical instances that implement the richer capability (e.g.Maybe,Optional,Try,Either,List,Stream). The error typeEofmonadErroris inferred from the assignment target; asking for a capability the instance does not have fails fast with aClassCastException, exactly as calling a non-existent method would - Argument-carrying re-exports:
Instances.validated(Semigroup),writer(Monoid),eitherT(outer),maybeT(outer),optionalT(outer),readerT(outer),stateT(outer)andwriterT(outer, Monoid). The structurally-required dependency is now a compiler-enforced, self-documenting method parameter instead of something discovered by reading a constructor - One-idiom migration: The
Instancesfacade is adopted across ~196 test files, 66 runnable examples, and 71 book pages so the documentation and examples teach a single way to obtain an instance. TheMonadReader/MonadStateMTL capability classes andTraverse/Selective/Foldableremain a separate surface, intentionally out of scope for this facade and tracked separately - Reference material: New glossary entry, a Type-Class Instances section in the cheat sheet, and a
InstancesFacadeExamplerunnable example - Bifunctor law verification: The reusable Bifunctor law harness (
LawTestPattern/TypeClassTestPattern) now verifies the first-map (first(f, fab) == bimap(f, id, fab)) and second-map (second(g, fab) == bimap(id, g, fab)) consistency laws alongside the existing identity and composition laws, so every canonical instance (Either,Tuple,Const,Validated,Writer) is checked against all four laws; explicit named consistency tests added forEither(Left/Right) andTuple(#461) - Collection-path fold family:
ListPathandStreamPathgain the monoid-stylefold(identity, op)and theFoldable-stylefoldMap(Monoid, fn), plusfoldRightonStreamPath, matching the existingVStreamPath/VStreamContextfold surface so the same reduction reads identically across every sequence-like path and stays inside the path chain. Documented in the cheat sheet and the Foldable chapter, withCollectionPathsExampleupdated to fold without unwrapping (#462) - Effect Path
toString()standardisation: A sharedPathToStringhelper gives every Effect Path type one debugging-friendly, greppabletoString()convention: the round-parenthesis wrapper formTypeName(inner), a uniform angle-bracketed sentinel vocabulary (<deferred>,<stream>,<empty>,<pending>,<failed>), and bounded rendering for collection-backed paths (ListPath,NonDetPath,WriterPathlogs) with an explicit…(+k more)marker so a large backing collection never produces an unbounded log line.IdPathis now null-safe andLazyPathnever forces its computation when rendered; all changed path classes hold at 100% line/branch coverage (#530)
Expanded hkj-checker Compile-Time Diagnostics
This release grows the hkj-checker javac plugin from a single Path-type-mismatch check into a catalogue of twelve compile-time checks, adds per-check severity configuration, and consolidates the relevant OpenRewrite recipes into compile-time feedback. Several further candidate checks were investigated and deliberately not shipped (kept as passing characterisation tests that document why) because the targeted error is unreachable on modern javac, already caught by the compiler, or only detectable via a rot-prone heuristic; the strict no-false-positives policy is preserved throughout.
- Compile-Time Checks: Eleven new checks join
path-type-mismatch:effect-composition,transformer-missing-monad,free-switch-exhaustive,discarded-effect,state-t-mapt-arity,error-type-mismatch,kind-value-narrow,witness-arity,via-non-path,map-nests-effect, andmigration-nudge. Each is a companion to a real javac error or the sole signal for an otherwise-silent mistake (a discarded lazy effect, a silently-erased error type, a nested effect); the sole-signal heuristics default to a warning - Per-check severity: The plugin-argument grammar adds
severity:<id>=error|warnalongside the globalseverity=anddisable=<id>, so the warn-default checks can be promoted per project. Unknown ids and unparseable values are ignored so a typo never breaks the build - Recipe consolidation:
migration-nudgefolds theConvertRawFreeToFreePathandDetectInjectBoilerplateOpenRewrite diagnoses into advisory compile-time nudges;free-switch-exhaustiveandwitness-aritydo the same for the Free-switch andWitnessArityrecipes - Documentation:
tooling/compile_checks.mdis now the authoritative checker catalogue (every check, its default severity, and the configuration grammar); the effect/transformers/optics Common Compiler Errors chapters were corrected where they described errors modern javac no longer emits and cross-linked to the catalogue
Hardened hkj-openrewrite Recipes
This release audits and hardens the hkj-openrewrite migration recipes: correctness fixes, broader detection, type-safe matching, new 0.5.0 deprecation recipes, and a near-quadrupled test suite (9 → 34 tests).
- Arity bounds:
AddArityBoundsToTypeParametersnow emitsTypeArity.BinaryforKind2,BifunctorandProfunctor(previously alwaysUnary, which generated incorrect bounds), detects witness use across fields, local variables, the class hierarchy, nested generics and wildcard bounds (not just method signatures), and no longer emits malformed output (<Fextends …>); the existing-bound intersection case is also fixed - Type-safe detection:
ConvertRawFreeToFreePathandDetectInjectBoilerplateuse a type-attributedMethodMatcherinstead of rendered-string matching (a user type namedFree, fully-qualified calls, or static imports no longer mis-fire or get missed);AddHandleErrorCasenow also handles switch expressions with whole-word case matching; the three detect-only recipes emit OpenRewrite search-result markers instead of rewriting source with TODO comments - 0.5.0 deprecation migration: New
MigrateDeprecationsTo0_5_0recipe group renamesStateTKind.narrowK→narrowandKindValidator.narrowWithPattern→narrowHolder
Library and Build Refinements
This release also marks one wildcard-witness escape hatch for removal, makes the Java 25 toolchain self-provisioning, and lands the module-internal foundation for batched optic data access.
StateTKind.narrowKdeprecation:StateTKind.narrowKaccepts a wildcard-witnessKind<?, A>, bypassing the HKT witness type safety enforced everywhere else in the library; it has no callers and the type-safenarrow(Kind)already covers the use case. It is now@Deprecated(forRemoval = true)for removal in 0.5.0, with theMigrateDeprecationsTo0_5_0OpenRewrite recipe automating thenarrowK→narrowrename (#455)- Java 25 toolchain auto-provisioning:
settings.gradle.ktsapplies thefoojay-resolver-conventionplugin so Gradle downloads and provisions a matching Java 25 JDK automatically when the build machine does not already have one, removing a manual setup step for new contributors - Request-batching substrate: New module-internal
org.higherkindedj.optics.fetchpackage: a free-applicative-styleFetch<K, V, A>(Done/Blocked) andFetchApplicativethat plug into the opticmodifyFseam so a traversal whose focused values are loaded from a backend coalesces those N loads into one batched call (the classic N+1), with a transport- and datastore-neutralBatchLoadercontract and round-basedrunCached/runAsyncrunners carrying a per-run request cache. The package is intentionally not exported; it is the foundation for later data-access capabilities and carries no public API yet (#539)