Unifying Composable Effects and Advanced Optics for Java

Static Badge Codecov Maven Central Version Latest Snapshot GitHub Discussions Mastodon Follow

Higher-Kinded-J brings two capabilities that Java has long needed: composable error handling through the Effect Path API, and type-safe immutable data navigation through the Focus DSL. Each is powerful alone. Together, they form a unified approach to building robust applications, where effects and structure compose seamlessly. For services that need multiple execution modes, Effect Handlers let you define domain operations as data and interpret them differently for production, testing, or audit.

No more pyramids of nested checks. No more scattered validation logic. Just clean, flat pipelines that read like the business logic they represent.


The Effect Path API

At the heart of Higher-Kinded-J lies the Effect Path API: a railway model for computation where success travels one track and failure travels another. Operations like map, via, and recover work identically across all effect types, whether you are handling optional values, typed errors, accumulated validations, or deferred side effects.

// Traditional Java: pyramid of nested checks
if (user != null) {
    if (validator.validate(request).isValid()) {
        try {
            return paymentService.charge(user, amount);
        } catch (PaymentException e) { ... }
    }
}

// Effect Path API: flat, composable railway
return Path.maybe(findUser(userId))
    .toEitherPath(() -> new UserNotFound(userId))
    .via(user -> Path.either(validator.validate(request)))
    .via(valid -> Path.tryOf(() -> paymentService.charge(user, amount)))
    .map(OrderResult::success);

The nesting is gone. Each step follows the same pattern. Failures propagate automatically. The business logic reads top-to-bottom, not outside-in.

Explore the Effect Path API →


The Bridge: Effects Meet Optics

What makes Higher-Kinded-J unique is the seamless integration between Effect Paths and the Focus DSL. Where Effect Paths navigate computational effects, Focus Paths navigate data structures. Both use the same vocabulary. Both compose with via. And when you need to cross between them, the bridge API connects both worlds.

                    THE EFFECT-OPTICS BRIDGE

  EFFECTS DOMAIN                           OPTICS DOMAIN
  ══════════════                           ═════════════

  MaybePath<User>      ────┐         ┌──── FocusPath<User, Address>
  EitherPath<E, User>  ────┤         ├──── AffinePath<User, Email>
  TryPath<Config>      ────┤         └──── TraversalPath<Team, Player>
  IOPath<Data>         ────┤
  VTaskPath<A>         ────┤
  VStreamPath<A>       ────┤
  ValidationPath<E, A> ────┘
                            │       │
                            ▼       ▼
                       ┌─────────────────┐
                       │  .focus(path)   │
                       │  .toEitherPath  │
                       │  .toMaybePath   │
                       └─────────────────┘
                              │
                              ▼
                    UNIFIED COMPOSITION
                    ════════════════════

  userService.findById(id)        // Effect: fetch
      .focus(UserFocus.address()) // Optics: navigate
      .via(validateAddress)       // Effect: validate
      .focus(AddressFocus.city()) // Optics: extract
      .map(String::toUpperCase)   // Transform
// Fetch user (effect) → navigate to address (optics) →
// extract postcode (optics) → validate (effect)
EitherPath<Error, String> result =
    userService.findById(userId)           // EitherPath<Error, User>
        .focus(UserFocus.address())        // EitherPath<Error, Address>
        .focus(AddressFocus.postcode())    // EitherPath<Error, String>
        .via(code -> validatePostcode(code));

This is the unification that Java has been missing: effects and structure, composition and navigation, all speaking the same language.

Discover Optics Integration →


Two Foundations

The Effect Path API is built on two powerful functional programming pillars:

Higher-Kinded Types

Java lacks native support for abstracting over type constructors like Optional<A>, List<A>, or CompletableFuture<A>. Higher-Kinded-J simulates HKTs using defunctionalisation, unlocking:

  • Polymorphic functions that work across optionality, asynchrony, and error handling
  • Type classes like Functor, Applicative, and Monad with consistent interfaces
  • Monad transformers for composing effect stacks (EitherT, StateT, ReaderT)

Advanced Optics

Higher-Kinded-J provides the most comprehensive optics implementation available for Java. Working with immutable records means verbose "copy-and-update" logic; the Optics library treats data access as first-class values. The chapter opens with an annotation-led Quickstart and the Annotations at a Glance lookup table; you write a record, add @GenerateLenses and @GenerateFocus, and the processor writes a typed path builder for you.

  • Complete optic hierarchy: Lenses, Prisms, Isos, Affines, Traversals, Folds, and Setters
  • Annotation-driven generation for records, sealed interfaces, and enums; see Annotations at a Glance for the full surface
  • External type import via @ImportOptics for types you don't own
  • Spec interfaces for complex external types with copy strategy annotations (@ViaBuilder, @Wither, @ViaCopyAndSet)
  • Third-party integration with Jackson, JOOQ, Immutables, Lombok, AutoValue, and Protocol Buffers
  • Filtered traversals for predicate-based focusing within collections
  • Indexed optics for position-aware transformations
  • Profunctor architecture enabling adaptation between different data shapes
  • Focus DSL for type-safe, fluent path navigation with seamless bridging into external libraries
  • SPI-aware container types with automatic AffinePath and TraversalPath generation via cardinality-based widening, supporting 30 container types across JDK, Apache Commons, Eclipse Collections, Guava, Vavr, PCollections, and HKJ native types
  • Effect integration bridging optics with the Effect Path API

Effect Handlers

For services with complex domain workflows, Higher-Kinded-J provides algebraic-effect-style programming via Free monads and interpreters. Define your domain operations as sealed interfaces with record variants, then write different interpreters for production, testing, dry-run, or audit modes:

  • @EffectAlgebra generates boilerplate (Functor, smart constructors, interpreter skeleton)
  • @ComposeEffects composes multiple operation types into a single program
  • Mock-free testing via Id monad interpreters
  • Program inspection with ProgramAnalyser before any side effects execute

Why Higher-Kinded-J?

Higher-Kinded-J offers the most advanced optics implementation in the Java ecosystem, combined with a unified effect system that no other library provides.

FeatureHigher-Kinded-JFunctional JavaFugue OpticsDerive4J
Lens✓^1^
Prism✓^1^
Iso
Affine/Optional✓^1^
Traversal
Filtered Traversals
Indexed Optics
Code Generation✓^1^
External Type Spec Interfaces
Java Records Support
Sealed Interface Support
Effect Integration
Focus DSL
Profunctor Architecture
Fluent API
Modern Java (21+)
Virtual Threads
Effect Handlers / Free Monads

^1^ Derive4J generates getters/setters but requires Functional Java for actual optic classes


Path Types at a Glance

Path TypeWhen to Reach for It
MaybePath<A>Absence is normal, not an error
EitherPath<E, A>Errors carry typed, structured information
TryPath<A>Wrapping code that throws exceptions
ValidationPath<E, A>Collecting all errors, not just the first
IOPath<A>Side effects you want to defer and sequence
TrampolinePath<A>Stack-safe recursion
CompletableFuturePath<A>Async operations
ReaderPath<R, A>Dependency injection, configuration access
WriterPath<W, A>Logging, audit trails, collecting metrics
WithStatePath<S, A>Stateful computations (parsers, counters)
ListPath<A>Batch processing with positional zipping
StreamPath<A>Lazy sequences, large data processing
NonDetPath<A>Non-deterministic search, combinations
LazyPath<A>Deferred evaluation, memoisation
IdPath<A>Pure computations (testing, generic code)
OptionalPath<A>Bridge for Java's standard Optional
FreePath<F, A> / FreeApPath<F, A>DSL building and interpretation
VTaskPath<A>Virtual thread-based concurrency with Par combinators
VStreamPath<A>Lazy pull-based streaming on virtual threads

Each Path wraps its underlying effect and provides map, via, run, recover, and integration with the Focus DSL.


Learn by Doing

The fastest way to master Higher-Kinded-J is through our interactive tutorial series. Thirteen journeys guide you through hands-on exercises with immediate test feedback.

JourneyFocusDurationExercises
Core: FoundationsHKT simulation, Functor, Applicative, Monad~40 min24
Core: Error HandlingMonadError, concrete types, real-world patterns~30 min20
Core: AdvancedNatural Transformations, Coyoneda, Free Applicative~40 min26
Effect APIEffect paths, ForPath, Effect Contexts~65 min15
Monad TransformersWhen Path isn't enough, async + absence, stacking, MTL~90 min28
Expression: ForStateNamed fields, guards, pattern matching, zoom~25 min11
Concurrency: VTaskVirtual threads, VTaskPath, Par combinators~45 min16
Concurrency: Scope & ResourceStructured concurrency, resource management~30 min12
Resilience PatternsCircuit breaker, saga, retry, bulkhead~45 min22
Optics: Lens & PrismLens basics, Prism, Affine~40 min30
Optics: TraversalsTraversals, composition, practical applications~40 min27
Optics: Fluent & FreeFluent API, Free Monad DSL~35 min22
Optics: Focus DSLType-safe path navigation, container widening~35 min29

Perfect for developers who prefer learning by building. Get started →


Spring Boot Integration

Building enterprise applications with Spring Boot? The hkj-spring-boot-starter brings functional programming patterns seamlessly into your REST APIs, eliminating exception-based error handling whilst maintaining Spring's familiar conventions.

With Spring Boot Integration, you can:

  • Return Functional Types from Controllers: Use Either<Error, Data>, Validated<Errors, Data>, and CompletableFuturePath as return types with automatic HTTP response conversion.
  • Eliminate Exception Handling Boilerplate: No more try-catch blocks or @ExceptionHandler methods; errors are explicit in your return types.
  • Compose Operations Naturally: Chain operations with map and via whilst preserving type safety and error information.
  • Accumulate Validation Errors: Use Validated to collect all validation errors in a single request, improving user experience.
  • Handle Async Operations: Use CompletableFuturePath to compose asynchronous operations seamlessly.
  • Monitor in Production: Track EitherPath success rates, ValidationPath error distributions, and CompletableFuturePath async performance with Spring Boot Actuator metrics.
  • Secure Functionally: Integrate Spring Security with Either-based authentication and Validated-based authorisation logic.
  • Zero Configuration Required: Auto-configuration handles everything; just add the dependency and start coding.

Quick Example

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public Either<DomainError, User> getUser(@PathVariable String id) {
        return userService.findById(id);
        // Right(user) → HTTP 200 with JSON
        // Left(UserNotFoundError) → HTTP 404 with error details
    }

    @PostMapping
    public Validated<List<ValidationError>, User> createUser(@RequestBody UserRequest request) {
        return userService.validateAndCreate(request);
        // Valid(user) → HTTP 200
        // Invalid(errors) → HTTP 400 with ALL validation errors
    }
}

Get Started with Spring Boot Integration →


Testing With hkj-test

The hkj-test module ships fluent AssertJ assertion helpers for every Higher-Kinded-J type, so tests read in the same vocabulary as the code under test. Add it as a test-scope dependency and assert directly on the railway:

import static org.higherkindedj.hkt.assertions.EitherAssert.assertThatEither;
import static org.higherkindedj.hkt.assertions.MaybeAssert.assertThatMaybe;
import static org.higherkindedj.hkt.assertions.TryAssert.assertThatTry;

assertThatEither(result).isRight().hasRight(42);
assertThatMaybe(value).isJust().hasValue("hello");
assertThatTry(computation).isFailure().hasExceptionOfType(IOException.class);

Coverage spans the discriminated unions (Either, Maybe, Try, Validated, Lazy), the effect types (IO, VTask, VStream), the Reader / Writer / State trio, every monad transformer, the Free / EitherF algebras, the List / OptionalKind / Stream / Id Kind-narrowing wrappers, and the VTaskPath / VStreamPath / VTaskContext Path-and-context assertions. On Java 25 with --enable-preview, import module org.higherkindedj.test; brings every helper into scope in one line.

Explore hkj-test →


Getting Started

Ready to start? See the Quickstart for Gradle and Maven setup (including required Java 25 preview flags) and your first Effect Paths in 5 minutes.

For a one-page operator reference, see the Cheat Sheet.


Documentation Guide

Effect Path API (Start Here)

  1. Quickstart: Three runnable examples showing MaybePath, EitherPath, and ForPath in about 150 lines
  2. Core Paths: The railway model, the six core path types, composition, and basic ForPath comprehensions
  3. Optics Integration: Bridging Effect Paths with the Focus DSL
  4. Advanced Paths: Free monads, effect handlers, contexts, ForPath parallelism, and resilience
  5. Reference: Capability typeclasses, type conversions, compiler errors, and production readiness

Monad Transformers

For the cases where the Path API does not fit (a different outer monad, polymorphic library code, or integrating with raw Kind shapes).

  1. Path or Transformer?: The triage page; read this first to know whether the rest of the chapter applies to you
  2. Quickstart: Three runnable transformer examples in about 150 lines
  3. Stack Archetypes: Seven named patterns covering the most common composition problems
  4. MTL Capabilities: Stack-independent capability abstractions for polymorphic library code
  5. Capstone: End-to-end multi-capability workflow combining typed errors, configuration, audit, and async
  6. Common Compiler Errors: Six common errors and the fix for each

Optics

  1. Quickstart: Three runnable examples covering generated lenses, prisms and traversals, plus @ImportOptics for Jackson
  2. Annotations at a Glance: Every annotation, what it generates, and when to reach for each one
  3. Fundamentals: Lens, Prism, Affine, Iso, composition rules, and coupled fields
  4. Java-Friendly APIs: Focus DSL, optics for external types, Kind field support, and the Fluent API
  5. Integration and Recipes: Validation pipelines, core-type integration, and the cookbook
  6. Advanced Optics: Free Monad DSL and interpreters for programs-as-data
  7. Reference: Capabilities, conversions, compiler errors, production readiness, and consolidated decision trees

Effect Handlers

  1. Effect Handlers Introduction: Motivation, terminology, and when to use
  2. Effect Handler Reference: Defining, composing, and interpreting effects
  3. Payment Processing Example: Complete worked example with four interpreters

Foundations (Reference)

These sections document the underlying machinery. Most users can start with Effect Paths directly.

  1. Higher-Kinded Types: The simulation and why it matters
  2. Type Classes: Functor, Monad, and other type classes
  3. Core Types: Either, Maybe, Try, and other effect types
  4. Order Example Walkthrough: A complete workflow with monad transformers

History

Higher-Kinded-J evolved from a simulation originally created for the blog post Higher Kinded Types with Java and Scala. Since then it has grown into a comprehensive functional programming toolkit, with the Effect Path API providing the unifying layer that connects HKTs, type classes, and optics into a coherent whole.