The Landscape of Functional Programming in Java (Part I)

Çağatay YURTÖZ
The Startup
Published in
11 min readJul 13, 2020
Photo by Roman Mager on Unsplash
Photo by Roman Mager on Unsplash

Functional programming (FP) has proven itself as a concise, secure, and effective programming paradigm. It has attracted considerable attention in recent years, and many new functional programming languages have emerged, as well as existing languages that have adopted several functional programming concepts.

In 2015, I discovered my liking for functional programming while learning Scala. Despite mainly using Java, I've been eager to find similar features in the Java ecosystem and have tried implementing some myself. While not a full-time functional programmer, I'm still learning and enthusiastic about FP.

In this article, I will investigate what we can do to write more functional code outside of Java’s features. My aim is to explain some functional programming concepts to Java developers and then introduce and explore open-source projects that offer those features.

Be Functional Yourself

Is this article for people who have never started functional programming?

I do not think so. Functional programming begins with a set of concepts that must be grasped before using features of a language or a library; e.g. immutability, first-class and higher-order functions, pure functions, referential transparency, ensuring type-safety, avoiding or encapsulating side-effects. If you are not familiar with all these, I suggest the Wikipedia page which is a good source to have a brief idea about the concepts, Noel Welsh’s very neat introduction, and Alvin Alexander’s more detailed post and post that mentions its benefits. As a utility, Scala’s glossary may help to search foreign concepts and words quickly.

Java 8 And Beyond

Java 8 was announced as the first Java version to enable functional programming. Functions were “first-class citizens” for the first time in Java; meaning that “they could be bound to names (including local identifiers), passed as arguments, and returned from other functions, just as any other data type could” (quote: Wikipedia).

On the other hand, it is still not possible to write fully functional code in Java, and never will be. The principles of functional programming are not enforced in Java, because that would need a shift in almost all Java libraries, which are all creations of an imperative and stateful mindset. Also, most of the Java developers would have to leave their current programming style. It is unreasonable to think these will happen, because Java is a highly popular and industrialized language, plus backward compatibility has been its main focus for years.

Nevertheless, the features of Java 8 have become a gateway to writing functional code and should not be missed; on behalf of FP, what will be done next will also be based on them. I am not going to introduce Java 8 features; there are already tons of resources for it, but in order to proceed, you should know what Consumer, Supplier, Predicate is. You should make use of streams and lambdas and have a knowledge of common lambda operators like map, flatMap,filter. You should favor immutability — using the final keyword and immutable APIs as much as possible. It would be best to eliminate illegal states, like nulls or exceptions, by replacing them with meaningful values. It is essential to know what Java offers first.

What is missing?

I remember the arrival of Java 8. As a developer who had met Scala before it, I was not quite satisfied with it.

First of all, it seemed very verbose. It has no syntactic sugar, which is not just about the “style” of code as it is thought but about increasing the expressiveness of the language. Even if you write functional code, you still get some of Java’s verbosity. The lack of FP features — like type inference, tuples, and case classes/algebraic data types- also makes the verbosity situation worse. You can not define functions anywhere, you have to use @FunctionalInterface and it is a bit of a vibe-killer.

I am not a fan of stream() and collect() calls. Collectors are kind of a mess. I don’t know how many times I performed a Google search to properly use Collectors.toMap(). I have never experienced this while writing Scala.

In functional programming (and in your Algorithms 101 class), writing recursive functions is encouraged; recursive calls are stateless, more simple, and more elegant than loops. But we make use of them rarely in real life because recursion is expensive. Scala dealt with this issue by utilizing tail recursion optimization. However, Java could not deliver this feature and there is no sign of it will arrive.

Java still does not have a proper immutable collection API. Collections are mutable, and in order to keep things backward compatible, they made the newcomer immutable collections that implement the old collection interfaces. This is absolutely wrong. As a result, an immutable List created by Arrays.asList() call implements the mutable List interface, which defines add(...) method. Note that the add(...) is not an immutable- append operation, but it is still old mutable add. And what happens when you call it? You get a RuntimeException. It is dangerous, like a mousetrap for junior developers.

Optional<T> emerged to represent nullable values and cleared the ugly null checks. But what about more, like containers for representing exceptional values? For that purpose, Scala has Try[T], but there is no Try<T> in Java. It is necessary in an environment where the checked exceptions are still floating around.

I’m stopping the whining here. There’s a lot to say, but it’s hard to say anything original. So, which features could have helped us to write more functional code?

  • Tuples
  • Immutable collections with a proper immutable API
  • More lambda operators: foldLeft, foldRight, zip, unzip, slice, join, …
  • Lazy evaluation
  • Algebraic data types, sealed classes
  • Simple currying, lifting, composition, memoization
  • More monadic containers: Try, Either, IO, …
  • Pattern matching

Are these enough? It should be noted that the feature set of a functional programming language would overwhelm an OO developer; as for Scala, it has type inference, implicits, higher-kinded-types, mixins, traits, … well, actually a very extensible and a complex type system. But it would be unlikely to obtain these features with an external library. As Java developers, let’s settle with what we can find inside the Java ecosystem now.

Towards a Better Functional Programming

jOOλ

jOOλ or jOOL (L for lambda) is a library developed by the jOOQ team, a popular typesafe SQL-to-Java framework. The goal of jOOL is to complement what is missing in Java 8 regarding functional programming; therefore, it is not a complete FP library but rather a wrapper for Java 8 features.

jOOL provides tuples, and it defines functions with its Function type. Both Function and Tuple are implemented by classes like FunctionN, N being the argument count. It goes as far as Function16 so you can define tuples and functions having 16 parameters. You don’t need @FunctionalInterface anymore!

In addition to these, jOOL has Seq, an interface that extends Java’s Stream and provides missing operators on it.

A sequential, ordered Stream that adds all sorts of useful methods that work only because it is sequential and ordered(link).

And finally, Unchecked is the jOOL’s way to hide checked exceptions from the compiler. It allows you to write intact lambdas, without try-catch, by wrapping the call that throws an exception.

jOOL is very lightweight and it fills some missing parts of functional Java with very little overhead. But if you are looking for a more comprehensive framework, the next one might suit you.

Vavr

Vavr (formerly called Javaslang) is a complete library for functional programming. I have been an enthusiast for Vavr in my recent projects, therefore I desired to explain its features in more detail, and show why they matter, especially for FP novices.

Functions & Tuples

For starters, Vavr defines Tuples with a very concise syntax. It even allows you to reach tuple elements with underscore notation, like _1.

Vavr allows you to define functions with 8 parameters at maximum — which is lower than jOOL’s limit of 16 for higher-order functions. Besides, Vavr has function composition, lifting, partial application, currying, and memoization. Starting with composition, you can compose functions with andThen or compose methods.

I would like to briefly explain what they are before exemplifying the next features:

  • Partial Function: a function defined for specific values only. For example, division operation is not defined when the divider is zero.
  • Lifting: Process of turning a partial function into a total function. For undefined values, it returns an Optional, or something similar.
  • Partial Application: Converting a function into another function that takes a subset of its original parameters — by fixing the other parameters.
  • Currying: Currying is a process that resembles the partial application, but the returned function takes only one parameter in each step. “The technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.”(quote: Samuel Pouyt).
  • Memoization: Caching the result of an expensive function call, when the function is called again, it returns the computed value. You probably remember memoization from the dynamic programming class.

Values

Values are Vavr’s implementation of monadic types. Monads are basically wrappers that apply effects to values or computations and share common behaviors like map, flatMap, peek, orElse, getOrElse , andThen etc. As an example, Java’s Optional is a monad (or looks like a monad). If you are interested in the theory of monads, I have found the following sources very adequate and understandable: 0, 1, 2, 3, 4.

Option

Option serves the same purpose as Optional in Java 8. There is a slight difference, which is explained here. I will not go into detail to leave more space for the others.

Try

Vavr has Try<T>. Thank God! For those unfamiliar, Try is a type that represents a computation that may outcome in a value or with an exception. Thanks to Try monad, all exception throws can be chained and processed functionally, preventing the program flow from breaking. No more ugly-nested try-catch blocks!

Try has two possible subtypes, Success and Failure. If it is a Success, it contains the result value; if it is a Failure, it contains an exception. For Failure scenarios, Try has recover(...) and recoverWith(Try<T>...) convenience methods, both can be defined for specific exception types. Try also supports the try-with-resources feature which was introduced in Java 7, if you call the withResources factory method and provide the AutoCloseable resource.

Either

Either is a value container for two possible types, Leftor Right. According to the common convention; if Either is a Left it contains an error, if it is Right it contains a value. That makes Either right-biased; means that functions such as map and flatMap only apply to the Right — which contains the result of the successful operation.

Lazy

Lazy<T> is a value container that represents a computation that will be computed when it is called, i.e. lazily. When it is computed, it always returns the same value anymore, because the value is memoized.

Future

Vavr’s Future<T> is a wrapper for asynchronous computations and it is much like Scala’s Future. It can also be converted to Java’s CompletableFuture.

Future is non-blocking by default, therefore it needs an ExecutorService to execute functions. By default it employs an Executors.newCachedThreadPool() but a different executor can be provided as an argument when the Future is created.

Future can provide common Value operations, like map, flatMap, andThen as well as Future-specific methods. You can call isComplete() to check if the Future has been completed, or call await() to block the current thread until the Future is completed. When a Future is completed, it is represented by a Try. The resulting Try may be a Success or a Failure, to handle these cases there are onSuccess and onFailure callbacks, as well as transform method which takes the resulting Try itself.

Validation

Validation<T,Y> is a structure that can be used for any data validation use case. It has two possible subtypes: Valid and Invalid, and takes two type parameters for valid and invalid situations, respectively.

Validation is different from other value containers; it does not short-circuit when it first encounters an error. When you flatMap two Trys, if one of them is a Failure you get a Failure as the resulting Try. However, validation accumulates the errors instead of doing this, which is very useful when you need to validate multiple fields, like a web form. Due to this feature, Validation is defined as an applicative functor instead of a monad.

In order to make use of Validation, you expose your validation logic as Validor Invalid instances. Validation.combine(...) method gathers these instances and creates a builder. ap(...) method on the builder triggers the validation and also allows you to define what you are going to do if the operation is valid. In the end, you can convert the Validation to an Either, or call .isPresent() and .get() on it, or call .fold(…) to transform both error and data to a single value.

For-Comprehension

Let’s say, you have multiple Trys that must be combined. What is the known way of doing that? You call flatMap on them, sequentially, like in the code below:

However, this looks a bit unreadable. How about, if you were able to iterate on Try’s, and accumulate the results, like combine in Validation? Well, there is a solution for that. For-comprehension of Vavr iterates over the Values and yields a final result.

The result from the yield is a lazily evaluated Iterator, therefore in order to process the values into their final form, forEach part must be called.

There is more …

In this article, I discussed what we can expect from a functional programming library for starters. Although I focused heavily on Vavr, the main goal was to establish the foundation and assess the libraries that meet our expectations. Other libraries that offer similar features and are worth mentioning include:

Cyclops

Cyclops is a comprehensive functional programming library that enhances Java’s capabilities by adding a wide range of functional programming constructs and utilities. Not only designed to work seamlessly with Java, Cyclops provides integrations to other functional programming libraries — like Vavr and FunctionalJava.

  • Functional control structures —Optional, Try, Either, Validation
  • Functional helpers. Extensions for Java’s Function, Predicate, and Consumer, also helpers for currying and memoization .
  • Lazy Evaluation
  • Wide range of immutable data structures
  • Reactive collections and streams. They can be integrated to RxJava and Reactor.
  • Pattern Matching
  • Advanced functional data structures — control, arrow, free monad, type classes, etc. via cyclops-pure module

Atlassian Fugue

I learned about Fugue from Svend Vanderveken’s comment on the post. Fugue provides a subset of functional programming features, making it lighter and easier to integrate into existing Java projects. It offers Option, Either and Try types help manage nullability and errors. Additional helper utilities are also available for Java’s native Function, Supplier, and Iterable operations. Fugue also has Monoid, SemiGroup, and Optics extensions for those interested in more advanced topics. Its maintenance by Atlassian is also an advantage because the lack of maintainers is often a problem in open-source projects.

Functional Java

Functional Java is possibly the oldest functional programming framework in existence. It enabled writing functional code in Java7 and, therefore, accomplished a lot. The library also has a huge set of features:

  • Monads and functors: Includes Option, Either, Validation, and Try for handling optional values, errors, and side effects.
  • Immutable collections: Provides immutable List, Set, Map, Array, Tree and TreeMap structures for safer, side-effect-free programming.
  • Pattern Matching: Enables more expressive and concise code by simplifying the handling of complex data structures.
  • Other functional abstractions: monoid, semigroup, natural, random number generator, reader, writer, state, IO, optics.
  • Concurrency support: Actor and Promise implementations.

However, I am unsure about the project’s current status. The website appears to be down, and it is unclear if an active maintainer exists. It is still a sound library in to study and learn the topics, but I suggest not getting involved until the project is back on track.

What’s next

I plan to explain more topics and explore various libraries in the upcoming articles. In addition to what I mentioned here, another topic I find important is functional data structures. The next post will be about this.

Until that, keep it stateless!

--

--