Spring Integration

"If thought corrupts language, language can also corrupt thought."

– George Orwell, Politics and the English Language


Exception-based error handling corrupts thought. When a method signature says User getUser(String id), it lies by omission. The user might not exist. The database might be down. The ID might be malformed. None of this appears in the signature. The exceptions, when they come, arrive as surprises, handled in catch blocks scattered across the codebase, their semantics unclear, their taxonomy baroque.

Functional error handling clarifies thought. When a method returns Either<DomainError, User>, the signature tells the truth. Failure is possible. The error type is explicit. Callers must acknowledge this reality; the compiler ensures it. The code becomes honest.

This chapter bridges functional programming and enterprise Java. The hkj-spring-boot-starter allows Spring controllers to return Either, Validated, CompletableFuturePath, VTaskPath, and VStreamPath directly. The framework handles the translation to HTTP responses: Right becomes 200 OK; Left becomes the appropriate error status. Validation errors accumulate properly. Async operations compose cleanly. Virtual thread operations run without thread pool configuration, and SSE streaming works without WebFlux or Reactor.

For applications that compose multiple effects, EffectBoundary bridges Free monad programs into the existing handler ecosystem. @EnableEffectBoundary auto-discovers @Interpreter beans, combines them, and registers an EffectBoundary that interprets programs and returns IOPath or FreePath, which the existing handlers convert to HTTP responses.

The integration is non-invasive. Existing exception-based endpoints continue to work. Migration can proceed incrementally. But as more of your codebase adopts functional error handling, a subtle shift occurs. Errors become data. Control flow becomes explicit. The language of your code begins to clarify your thought rather than corrupt it.


The Transformation

    ┌─────────────────────────────────────────────────────────────┐
    │  EXCEPTION-BASED (Traditional)                              │
    │                                                             │
    │    User getUser(String id)    ← What can go wrong?         │
    │                                                             │
    │    @ExceptionHandler(UserNotFoundException.class)           │
    │    @ExceptionHandler(ValidationException.class)             │
    │    @ExceptionHandler(DatabaseException.class)               │
    │    ...scattered across the codebase                         │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  FUNCTIONAL (With hkj-spring)                               │
    │                                                             │
    │    Either<DomainError, User> getUser(String id)            │
    │           ↑                                                 │
    │    Errors visible in the type signature                     │
    │                                                             │
    │    → Right(user) automatically becomes HTTP 200             │
    │    → Left(NotFoundError) automatically becomes HTTP 404     │
    │    → Left(ValidationError) automatically becomes HTTP 400   │
    └─────────────────────────────────────────────────────────────┘

What the Starter Provides

FeatureBenefit
Either return typesTyped errors in controller signatures
Validated return typesAccumulate all validation errors
CompletableFuturePath return typesAsync operations with typed errors
VTaskPath return typesVirtual thread async via DeferredResult, no thread pool needed
VStreamPath return typesSSE streaming on virtual threads, no WebFlux/Reactor needed
FreePath return typesComposable effect programs interpreted at the boundary
EffectBoundaryAuto-wired interpret-and-execute for Free monad programs
@Interpreter beansSpring-managed interpreters with DI and profile switching
Automatic status mappingError types to HTTP status codes
JSON serialisationConfigurable output formats
Actuator integrationMetrics for functional operations
Security integrationFunctional authentication patterns

ReaderPath vs @Autowired

If you use Spring's dependency injection and wonder how ReaderPath compares, see ReaderPath vs Spring Dependency Injection in the Advanced Effects chapter. The short version: Spring DI is best for application-scoped singletons; ReaderPath shines for per-request context that varies at runtime.


What You'll Learn

In This Chapter

  • Spring Boot Integration – Configure Spring to accept Either, Validated, CompletableFuturePath, VTaskPath, and VStreamPath as controller return types. The framework automatically maps Right to 200 OK and Left to appropriate error statuses. VTaskPath provides virtual thread async, VStreamPath provides SSE streaming.
  • Migration Guide – A practical path from exception-based error handling to functional types. Start with one endpoint, prove the pattern, then expand incrementally.
  • EffectBoundary Integration – Compose multiple effect algebras into a single program and interpret them at a clean boundary. @EnableEffectBoundary auto-discovers @Interpreter beans, combines them, and bridges Free monads into the existing *Path handler ecosystem. Progressive adoption from a single @Bean to full auto-wiring.

Chapter Contents

  1. Spring Boot Integration - Using Either, Validated, and *Path types in controllers
  2. Migrating to Functional Errors - Moving from exceptions to functional error handling
  3. EffectBoundary Integration - Composable effects with auto-wired interpreters

Next: Spring Boot Integration