Let’s Turn Java Upside Down! 🤘

Arindam Paul
The Startup
Published in
17 min readApr 25, 2020

--

Introduction

This is a true story of how I learned Exception Handling in Java. Somehow from the beginning, I struggled with it badly, I could never have my head wrapped around how to really use Exceptions, when to define new of them, specially to verify the correctness of a program at hand. Below quote succinctly summarizes the issues with classic OOP exception handling,

“Every time you call a function that can raise an exception and don’t catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn’t think about. “ — Joel on Sofware (Oct. 2003)

I never planned to learn a functional library like Vavr(formerly known as Javslang) for Java, but with this, I finally got a fundamentally different idea of how to deal with Exceptions which we will cover in details in this post.

Moreover, the more, I learnt about other building blocks of the library, the more it became very clear to me that, it’s scope not just limited to this one critical scenario of dealing with Errors, but solves much broader problems on a day to day basis. So, the last section gives a taste of using Vavr as a functional library and kind of things we can achieve with Vavr (Tuples, Function, Option, Either etc.) But overall focus will be on solving this problem.

Drawbacks of Classical Exception Handling

Well, in the beginning it all made sense to me, try/catch blocks, throwing Exceptions from one part of the code and catch it in higher up the hierarchy of try/catch.

In Java, it’s common to throw Exceptions and just deal with errors “later” in some try/catch up upon the hierarchy. It’s also common to just throw or convert an Exception to RuntimeException to avoid adding throws on every function — which is a bad smell anyway. Often, the “later” is the main function which inevitably doesn’t know how to handle the deep nested Error, and the program will simply exit.

There are many issues with this approach, and I would like to take this opportunity to build some foundation of why it is so, once it’s clear that we need an alternate, understanding the alternative will be easy, so let’s get started.

Exceptions Behaves like GOTOs, no better than that:

The biggest drawback if any of an Exception is it completely branches off from the regular return type of the function. More like a GOTO statements, and we all know go to statements are sinister.

They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn’t reveal potential bugs.

They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don’t catch it on the spot, you create opportunities for surprise bugs. Soon, it becomes impossible reason about all possible exit paths of a function or program.

Checked Exceptions are Bad:

Checked Exceptions add a lot of Bloat to the code and does little of actual recovery. First of all, they enforce you to declare your functions as throws of the same exception type (meaning it will be handled higher up) or handle them right there which might not be the intention. More over, they create problems with interfaces, because you can’t extend or change the exception type it throws and if you have to throw a new type of exception most of the times you will end up wrapping it up in a RuntimeException or an unchecked Exception. That’s why languages like Kotlin, Scala, C#, C++ doesn’t even have a concept of Checked Exceptions.

Lambdas can’t throw checked Exceptions

A big part of modern Java is about using lambdas introduced in Java 8 for writing more expressive code. But, lamdas can only throw unchecked Exceptions, because all the functional interfaces don’t throws anything. Because lambdas are so ubiquitous with Java 8 and is present everywhere, meaning you are just hiding tons of errors in your code which you don’t know about.

There is no easy way around this, there are mechanisms like SneakyThrows which allows us to throw checked exception without explicitly declaring them (which can be used in Lambda Expression). But again it is like abusing the system for something which it is not meant to do.

Exception Abuse (Swallow, Re-throw, Wrapping etc.)

When you catch an exception and do nothing with it, is called swallowing an exception. Most of the time, it would be of a little meaning for us to do this because it doesn’t address the issue and it keeps other code from being able to address the issue, as well.

Even worse is a scenario where you throw exceptions from finally blocks. It is very very bad. It switches the result to caller to handle the issue of finally block and the Error in catch block is lost forever. Take a look at the language specification around the same,

If execution of the try block completes abruptly for any other reason R, then the finally block is executed, and then there is a choice.

If the finally block completes normally, then the try statement completes abruptly for reason R. If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

Another very common practice is to wrap the original exception into a new exception and then throw that. It creates unnecessary stacks and multiple duplicate stacks which are not useful to trace the issue. Look at a simple example here,

Whenever we create a new instance of RuntimeException it has its has its own stack trace filled at point of creation. So, when we wrap an exception with another new instance of an exception, we get a lot of bloated stack trace which has nothing to do with the actual cause of error.

In order to solve this here, at least, you can either call Throwable.getCause().getStackTrace() when you catch RuntimeException or set stack trace of RuntimeException to one from original exception before throwing RuntimeException. But again, that’s ugly on the code.

Exception Hierarchy and Cost of Throwables

All exception and errors types are sub classes of class Throwable, which is base class of hierarchy. One branch is headed by Exception. This class is used for exceptional conditions that user programs should catch. NullPointerException is an example of such an exception. Another branch, Error are used by the Java run-time system(JVM) to indicate errors having to do with the run-time environment itself(JRE). StackOverflowError is an example of such an error.

Exception Objects are much more expensive then you think it is. Don’t be under the impression that creating an Exception is similar to creating a Java POJO with some additional properties(like Error message). Well it's not for free, but still cause for performance issues. The real issue comes up when you create the object itself. Once created, Throwing a Throwable or reusing it (or one of its subtypes) is not a big deal.

Creation of Exceptions is a big deal because usually it will be a call to synchronized Throwable.fillInStackTrace(), which needs to look down the stack and put it in the newly created Throwable for the current thread. This can affect the performance of your application to a large degree if you create a lot of them.

More on this: http://normanmaurer.me/blog/2013/11/09/The-hidden-performance-costs-of-instantiating-Throwables/

The Value of Values

The Fundamentally different approach that a functional realm takes do deal with Errors is to represent them and treat them as plain values just like any other value or Object. No special treatment is good and it allows us to write more direct and reasonable code which we will see in a moment.

For example, Go lang does not provide conventional try/catch method to handle the errors, instead, errors are returned as a normal return value.

Great inspiration on this front is taken from the famous talk of Rick Hickey the creator of Closure programming language named “The Value of Values”. Even if you don’t know or need Closure Programming for this, its highly recommend watch.

In order to fully appreciate the idea, we will take a long detour now, into the world of Functional Programming for a moment, we will see what additional constructs, capabilities and tools it provides which we can leverage for solving this mess.

Box/Container Model for Data

I am no expert in Functional Programming, so I will try to avoid the technical/mathematical terms as much as possible. My focus will be to give you the essence of a thinking in terms functional constructs and ideas naturally.

So, the first big IDEA of Functional programming is about representing Values and Types. For a beginner, I would like to think about them with a Box or a Container analogy. Basically, what I am saying it say we want to represent the Value 2, the value itself is the real thing but without a Type it can’t have much value to us. You might think, wait a second, we have a type of 2, which is Integer. You are correct, it is an integer, see just by the fact of it being an integer, you can think about possible modifications and operations that can be done to this value. So, in one and very real sense Type gives value a shape or structure which then can have more meaningful operations and ideas around them.

A Box Containing Data

But, we want to take this idea to the next level. Let’s imagine a Box around the value 2 and we call the value is contained in a Type of Box. So, right now, we can say there is a Box (which is Type or Context) which holds an Integer (which is again Type or Context) of value 2. But you might ask, why would we want to do this in the first place.

Well for many very good reasons for this,

  • One, just by saying its a Type Box and making Box hold any type of data internally, we have build a wrapper or context oursevles where we can now define operations which has nothing to do with what’s inside the Box but everything to do with the Box itself. This is where functional programming shines, because it can now deal only at an abstract level still doing many meaningful things for us.
  • Second, it gives us an opportunity to Seal the values so that they can’t be modified again. At the time of creation of the Box whatever value we create it with becomes the frozen value for the Box/Container as long as it lives. This gives us immutability and provides even more capabilities on the context of Box and doesn’t care what it holds. In fact, all immutable data structures are basically a containers like this which doesn’t allow any modification post construction.
Capturing an Empty/Error Scenario
  • Third and final bonus is that in this abstraction, we can even represent absense of values like a MayBe Box. Very similar to how Java’s Optional is represented, even if there is no value, still the container exists and so does the operations on them (like you can safely map() over an Optional which may be null or Empty) . This lets us build very powerful and expressive code which deals with both the presence and absence of data at the same time and behaves accordingly (because Type is what defines how a particular operation is performed or what it does). For our discussion, we will explore this even more with code branching and error handling.

Functional Thinking in terms of Boxes

Well the foundation of the Boxes analogy was great, but Boxes themselves are abstract and might not be that useful if we can’t leverage the value it contains. So extending the example above, say we have a Box(2) with us and we want to add value 3 to it.

Simple Operations on Values

So how can we do this, let’s see. Earlier we said that the value itself is immutable but that doesn’t limit us from reading or extracting the value from the container to look at it, they are open for reads. This is what we try to understand and use. So the operations defined on the Container can help you get the Data back. Like when you call .get() on an Optional type in Java, we are doing the same.

But, if we want to do some operations on them in a chain, we can use methods like map() defined in the Box context which takes a lambda or function as argument and applies it to the extracted value, and finally Box it back up so that more operations can use the new Boxes now,

Take a look at the example below.

Safe Operations on top of Box Abstraction

Don’t get confused on the words unwrap and rewrap there. It might seem like the operations are happening on the same Box but they are indeed happening in different Boxes. Here, the most important thing to note is what happens when there is no value. Well, because we can now define map() on the box in such a way that if the extraction fails, even if we pass an operation it ignores it altogether and doesn’t apply it. You can see how this Box abstraction is already helping us dealing with code branching without if/else and other things.

Safe Operation even for non-existent value

We will take a look at such a Union type called Either in Functional Programming (I will be using JavaScript for this example as it’s easier to pass functions around) which can handle code branches and can deal with Errors. Either is a union type which is either a Left or a Right, generally, Right type represents happy path and Left type represents something went wrong. Similar operations are defined in both Left and Right in order for Either type be agnostic of what it is dealing with. Once, all operations are done, to get the final result we call a fold() function defined on those types, we pass a failure scenario and a success scenario to the fold() method.

Now based on the Type, fold() magically either return an Error if the final Box was a Left() or it will return a result if the final Type is a Right(), let’s see what the code would look like for this first.

Now see how beautifully we can use them,

I wanted to show you a complete example without any libraries and extra baggage. In practice, any good functional library will have these Types and operations on them already defined. In the context of Java, Vavr provides all of these and much more. We will see an example of error handling with Either type first and then with even better options. Without any further due, lets dig into Vavr.

Introduction to Vavr

Vavr core is a functional library for Java. It helps to reduce the amount of code and to increase the robustness. A first step towards functional programming is to start thinking in immutable values. Vavr provides immutable collections and the necessary functions and control structures to operate on these values. The results are beautiful and just works.

You can think of Vavr as the new Google Guava (Java8 onwards)

Java 8’s lambdas (λ) empower us to create wonderful API’s. They incredibly increase the expressiveness of the language.

Vavr leveraged lambdas to create various new features based on functional patterns. One of them is a functional collection library that is intended to be a replacement for Java’s standard collections.

The data structures make the big difference. Vavr has rich data structures. They provide us with simple methods that can be composed to solve complex problems. Here is a top level view of the data structures and control flow you get from Vavr.

Also, there is a Lazy implementation as a Type as well which doesn’t fit into the Data Structures as such, so from a namespace perspective, Vavr looks like this.

You can see it has huge set of functional Data Structures and APIs which play nicely with each other to build more complex execution pipelines and control flow. It pretty much covers all of JCF (Java Collections Framework) and add some unique features on top of that.

Some unique features of Vavr over Java/JCF, are,

  • Option<T> which is container for Optional value is serializable, Java’s native Optional is not serializable and creates a lot of problems while passing objects around.
  • It provides a notion of Tuple Data Structure where a Tuple a container for heterogeneous values which can be passed around, unlike a collection which adheres to a particular type. Using Tuple we can return more than one value from a function easily and model our error handling like Go lang mentioned above.

Functional Exception Handling using Vavr

So we have covered a lot of ground to come to the meat of the section, as to how to use them for real scenarios, how everything comes together. Most importantly, how well these play with existing libraries and APIs which might not be a Vavr Box or a Container. But above all, I generally try to stick to these two principles as well which working with Exceptions,

  1. Try not to throw an exception of my own, I generally never create Custom Exceptions.
  2. Always catch any possible exception that might be thrown by a library, and deal with it immediately or as early as possible.

So, with these in mind let’s try to give an attempt for exception handling with our much discussed Either Type.

Let’s try with Either

So, let’s take a classic example of division by zero, which causes an RuntimeException (Arithmetic Exception).

We define a function which takes a dividend and divisor we don’t know their values upfront. So, the result of the operation can be modeled as a Success with a Right() and Exception can be modeled as a Left(). Let’s take a look at the following code,

You can see this will produce an output which works for the happy path but blows up on the exception side. Why? because Either is not a special Type which will magically know how to deal with Errors, it just allows a programmer to be more expressive. If we have more operations on the chain it will break in the above scenario.

Either done right

We can fix this, by explicitly returning a Right() or a Left() from our function, take a look,

This will produce the result we intended. It will give either a Right() or a Left() based on the success or failure of the operation. Also, check for the caller exception handling becomes so much easier now.

Even better with Type Try<T>

The above flow is so common in practice, that Vavr provides even more direct and clear Type to deal with Exceptions using Try Type.

A Try type is meant to deal with an exception, we don’t need to explicitly do our own Try/Catch at all. Take a look at the same function using Try Type.

And we get the outcome types as Success() or Failure() wrapping the values for us.

More Functional Examples Try using Vavr

In this section, we will look at some more examples and scenarios of dealing with Errors using Vavr.

Let’s say we have an API at Amazon which provides the recommended suggestions on Products. We have a API getPersonalRecomendations(final String customerId) which returns a list of recommendations for that particular customer.

If that call fails, we want to fallback to a generic recommendations API getGeneralRecomendations() which is little bit degraded but still better than not showing anything.

Lastly, if both of these APIs fail, we would want to show some cached recommendations to the customer. You can already see how the code branching will look like and how many checks we need to have in place to implement this.

Using Vavr, this becomes as easy as reading english to understand the intent of the code. Let’s take a look at how we would want to write code for the above scenario.

Immutable and Persistent Data Structures

Immutable data structures cannot be modified after their creation. In the context of Java they are widely used in the form of collection wrappers.

List<String> list = Collections.unmodifiableList(otherList);

// Boom!
list.add("why not?");

There are various libraries that provide us with similar utility methods. The result is always an unmodifiable view of the specific collection. Typically it will throw at runtime when we call a mutator method. In Vavr, you can imagine the Containers or the Holders of the Data to be immutable.

But, if I have to really think about it, let’s say we need to merge two arrays. Should we be creating new Arrays keeping all others immutable at every step of the process, that would be horribly wasteful. So, we conceptually want a similar guarantee but the implementation needs to be better. This is where persistent data structures come into play.

A persistent data structure does preserve the previous version of itself when being modified and is therefore effectively immutable. It only changes the value it needs to and updates the version. Fully persistent data structures allow both updates and queries on any version.

// = List(1, 2, 3)
List<Integer> list1 = List.of(1, 2, 3);
// = List(0, 2, 3)
List<Integer> list2 = list1.tail().prepend(0);
How Persistent Data Structures Handle Immutability

Many operations perform only small changes. Just copying the previous version wouldn’t be efficient. To save time and memory, it is crucial to identify similarities between two versions and share as much data as possible between them. This model does not impose any implementation details and gives you performant immutable Data Structures to play with.

Functional Data Structures, also known as purely functional data structures, these are immutable and persistent. The methods of functional data structures are referentially transparent. (Given the same input the output is always same)

Conclusion

This was a little exploratory write up, I wanted to structure it exactly they way I learned about them. Hopefully, you have enjoyed the read. In the end, just from the context of Vavr and Exception handling in general, we can summarize the take aways like this,

  • Exceptions are meant to be dealt with immediately or as early as possible. It is best done when you don’t expect your caller to recover from it.
  • Absense of value can be modeled in Containers or Boxes keeping the semantics of the operations on them same using Option, Try etc. Option of Vavr is better than Java’s Optional because it’s serializable.
  • Try Type specially can always represent something which may result in Error.
  • Either Type can go beyond just handling Errors can be used in general for any type of code branching. Even can be used to model Errors in Simple values or POJOs (Go lang).
  • If you have a situation where, there is Try within a Try and you want to flatten them out, try to use flatMap() operation which will unfold all the nested Try Types for you before it proceeds.
  • Lastly, I look at Vavr as my new collections library for doing anything in Java.

Well, that was about it, if you want learn more or have specific questions, you can reach out to me in responses or on twitter. Happy Coding!!

--

--