Java Exceptions
Our technology trend Hunters are relentless. In this new edition of Inspiring Technology, they ask whether Java exceptions are being misused. Read on!
In current developments, whenever there is an error, Java will typically throw an exception. For example, when there is an unfulfilled business validation, records not found in the database when accessed with ID... even third-party libraries.
However, these exceptions may or may not be handled in the same class where they are thrown, and most often they are not. In a three-layer architecture, an exception thrown in the data access layer can even end up being handled in the presentation layer, which breaks the pattern itself and leads to coupling.
It’s clear that handling exceptions in this way becomes a sophisticated GOTO and breaks the principle of least astonishment, which states that a programme’s behaviour should be obvious, consistent and predictable. Even if we could break the dependency between layers through the use of Exceptions Translators, the basic problem would still exist, which is the use of exceptions for something that is not exceptional.
According to Oracle:
Exceptions are used to handle exceptional events. When an exception is thrown, is it an exceptional or relevant event? What is the difference? A user inputs some data via a form and sends it. The data is validated on the server and found to be incorrect. In this scenario, it’s fairly common for a user to enter the wrong data, so it cannot be considered an exception. However, it is relevant to the system, as it cannot allow the user’s operation to continue.
Besides, since exceptions are acting as GOTOs, there is a small additional performance issue.
So how can these problems be solved?
Vavr to the rescue
Vavr is a library based on the functional paradigm that has been gradually making its way into Java since version 1.8 and allows exceptions to be handled as normal data. It makes use of the concept of monads already introduced in Java 8 for data types such as Optional or Streams.
Vavr proposes the use of five basic data types:
1. Either: Syntactically it is a value of two possible types (tuple), Left or Right. You get Right from an operation and Left when the operation fails:
2. Option: Similar to Java’s Optional class. In this case, it can also become Either. In Java 8 Optional, if a .map is called and results in a null, nothing would happen because it would not be executed. With Option in Vavr, the result would be a NullPointerException. The Vavr developers consider it correct by adhering to the requirement that a .map operation called on a monad must maintain computational context.
3. Try: It is a monadic type which represents a computation that may end in either a success or a failure.
The toEither() method returns an Either<Throwable,T> where T is the data type returned by the failPruneMehtod method.
It also allows us to implement a functional version of a try-catch block.
4. Validation: An Applicative Functor that allows the accumulation of errors from different functions.
A Validation object can be easily converted to Either through the .toEither() method.
Seq is a Vavr data type that expresses a collection of values. It can be easily converted to a list using .toJavaList().
5. Lazy: A monodic value representing a loosely calculated one-time value. That is, once calculated, it always returns the same value.
How does this apply to my project?
It’s often assumed that data coming into the business from a Rest service must always be correct, or else execution must be interrupted.
A user would be created as follows:
Finally, UserRepository would save it as follows:
Okay, but what about transaction control?
There is currently a feature that allows Spring to integrate rollback control into its transaction management based on Vavr monads.
However, an Aspect can always be made to call rollbacks programmatically as required.
Main advantages
When programming in this way, not a single exception is thrown despite all the validations made when creating the different objects (CreateUserCommand, User, etc.).
Any exceptions when accessing the database are taken into account by using Try. These exceptions are translated into business Error objects.
If we move the exceptions in an Error object, there would be no problem in losing the Stack Trace, as the data is within the exception itself.
Additionally, we have the advantage that, if there are several errors in the user data entered, all of them will be thrown, not just the error related to the first validation.
Finally, this is a fully functional approach that allows for faster development.
Disadvantages
Most Java developers come from imperative programming, so adopting Vavr as a functional library is often difficult for them, since, as we have seen, it is a different way of structuring programmes. However, we can use the library imperatively.
Using Vavr also implies coupling to the library itself, as there are no interfaces or anything to wrap this functionality. Therefore, if we use it, we must be aware of what it involves because, as we have seen, it modifies the structure of the programmes whether they follow an imperative or a functional/declarative approach.
Want to know more about Hunters?
A Hunter rises to the challenge of trying out new solutions, delivering results that make a difference. Join the Hunters programme and become part of a diverse group that generates and transfers knowledge.
Anticipate the digital solutions that will help us grow. Find out more about Hunters on our website.