Java interoperability — when the JDK is already enough
A library that demands you rewrite your codebase to use it is a library nobody adopts. jfp is designed to live at the edges of a normal Spring Boot service — the interop package converts in both directions, and this chapter is the honest accounting of when each side is the right tool.
The converters
public static <T> Option<T> toOption(Optional<T> optional) { return optional.map(Option::some).orElseGet(Option::none);}
public static <T> Optional<T> toOptional(Option<T> option) { return option.fold(Optional::empty, Optional::of);}public static <T> Stream<T> stream(Option<T> option) { return option.fold(Stream::empty, Stream::of);}
public static <T> Option<T> headOption(Stream<T> stream) { return Optionals.toOption(stream.findFirst());}The round trip is total: toOption(toOptional(o)) == o for every value, which means you can adopt jfp inside one service method and convert back at the boundary — incremental adoption without a flag day.
The decision table
| JDK feature | Use it when | The jfp alternative teaches |
|---|---|---|
Function<T, R> | Standard unary functions are enough | Fn1 keeps one vocabulary and carries the Kleisli helpers |
Optional<T> | A method may validly return nothing | Option exposes fold and makes the laws testable — use Optional in public APIs |
Stream<T> | Processing sequences, filtering, collecting | Foldable/traverse explain what collect is doing |
CompletableFuture<T> | Any async boundary | Result inside the future separates domain failure from execution failure |
| Exceptions | Unrecoverable, unexpected failures | Result/Validated model expected outcomes explicitly |
| Records + sealed interfaces | Immutable data, finite alternatives | They are the ADTs — Option is Some | None, no magic |
Two cells deserve emphasis. Optional is the right choice for a public method that may return nothing — Java developers know it, IDEs flag misuse, and jfp’s Option adds nothing a caller needs. And the JDK’s sealed interfaces already do the heavy lifting for sum types; the library types exist because they bundle the operations (map, flatMap, fold) with the alternatives, not because Java lacked the type.
The integration pattern
In a Spring Boot service the adoption path is narrow: repositories and clients keep returning Optional/CompletableFuture as usual; convert to Option/Result at the service’s inner edge where the functional pipeline begins; convert back (or fold into a response) at the controller. The functional core never leaks into framework annotations, and the framework never leaks null into the core. Part 20 wires exactly that boundary end to end.