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.

The starter also closes the other half of the loop. Once a service speaks typed errors over HTTP, the services that call it should understand them. @HkjHttpClient generates a declarative client whose methods return the same Effect Paths, decoding the response back into a typed error so the error channel survives the network hop. The server encodes; the client decodes; the typed error never collapses into a bare status code.

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.

New to the vocabulary?

This chapter uses Either, the Effect Paths (EitherPath, VTaskPath, …), and the "railway" model of success and failure tracks. If those are unfamiliar, The Effect Path API introduces them; the pages here then show how they meet Spring.


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   │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  AND BACK AGAIN (With @HkjHttpClient)                       │
    │                                                             │
    │    EitherPath<DomainError, User> getUser(String id)        │
    │           ↑                                                 │
    │    A client call that keeps the typed error                 │
    │                                                             │
    │    → HTTP 200 decodes to Right(user)                        │
    │    → HTTP 404 decodes to Left(NotFoundError)                │
    │    → HTTP 400 decodes to Left(ValidationError)              │
    └─────────────────────────────────────────────────────────────┘

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
@HkjHttpClient interfacesDeclarative clients that keep the typed error when calling other services
Automatic status mappingError types to HTTP status codes (inbound) and back to typed errors (outbound)
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.
  • Declarative HTTP Clients – The outbound inverse of the controller handlers. @HkjHttpClient generates a client whose methods return Effect Paths and decodes the response back into a typed error, so the error channel survives a service-to-service call. Wiring is configuration; you autowire your own interface.
  • 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. Declarative HTTP Clients - Calling other services and keeping the typed error
  3. Migrating to Functional Errors - Moving from exceptions to functional error handling
  4. EffectBoundary Integration - Composable effects with auto-wired interpreters

Next: Spring Boot Integration