logo le blog invivoo blanc

Handle Java Exceptions the Vavr Way

17 June 2024 | Java | 0 comments


Handling exceptions is a critical aspect of robust Java programming. While the traditional try-catch mechanism is effective, it often leads to verbose and less maintainable code. Along comes the Vavr library, which brings functional programming concepts to Java, including the powerful Either data type.
This article explores how Either can simplify and enhance exception handling, providing a more expressive and functional approach.

Overview of Either/Vavr Library

Either is a data type that represents a value of one of two possible types. An instance of Either is either a Right (typically representing success) or a Left (typically representing failure or an error). This dual nature makes Either a powerful tool for handling operations that can fail, encapsulating the success and failure paths in a single construct.
Vavr is a library that brings functional programming paradigms to Java. It provides immutable collections, functional data types, and control structures that enhance Java’s capabilities. Either is one of Vavr’s core data types, designed to help manage and propagate errors without relying on exceptions.


Immutable Data Types in Vavr

A key benefit of using Vavr is its support for immutability, which is central to functional programming. Immutability ensures that once an instance is created, its state cannot be modified, leading to more predictable and reliable code.


Examples of immutable data types provided by Vavr include:

  • Option : Represents an optional value (either Some or None).
  • Try : Encapsulates a computation that may result in an exception.
  • Future : Represents a value that may become available at some point.

Benefits of Immutability

  1. Thread-Safety : Immutable objects can be shared across threads without
    synchronization, simplifying concurrent programming.
  2. Ease of Testing : Immutable objects are easier to test because their state does
    not change.
  3. Predictability : The behavior of your program becomes more predictable since
    the state cannot change after creation.

To use Vavr in your project, add the following dependency to your Maven or Gradle configuration :


Gradle :

implementation 'io.vavr:vavr:0.10.3'

Maven :

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.10.3</version>
</dependency>

Traditional Try-Catch

In Java, the most common way to handle exceptions is by using try-catch blocks. This approach catches exceptions thrown within the try block and handles them in the catch block. Here is a simple example :

public class TryCatchExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Exception caught: Division by zero.");
        }
    }
    public static int divide(int a, int b) {
        return a / b;
    }}

What Are the Limitations of Traditional Try-Catch ?

  • Verbosity : Error handling code can become repetitive and cumbersome.
  • Scattered Logic : Error handling can be spread across many try-catch blocks,making it harder to maintain and understand.
  • Mixing Concerns : Business logic gets intertwined with error handling logic, reducing code clarity.


Either vs. Traditional Try-Catch

Traditional try-catch blocks often result in scattered error handling logic, making the code harder to follow and maintain. Either, on the other hand, encapsulates error handling within the return type, promoting a more functional and streamlined approach.


How Can Either Simplify Exception Handling ?

Let’s consider a basic example of division, which can throw an ArithmeticException when dividing by zero.

Using Either :

import io.vavr.control.Either;

public class EitherExample {
    public static void main(String[] args) {
        Either<String, Integer> result = divide(10, 0);
        
        result.peek(value -> System.out.println("Success: " + value))
            .peekLeft(error -> System.out.println("Failure: " + error));
    }

    public static Either<String, Integer> divide(int a, int b) {
        if (b == 0) {
            return Either.left("Division by zero is not allowed.");
        } else {
            return Either.right(a / b);
        }
    }}

How to Convert Exceptions to Either ?

You can also use Try.of() from Vavr to convert exceptions into Either seamlessly. This approach captures exceptions and wraps them into a Left.

import io.vavr.control.Either;
import io.vavr.control.Try;

public class EitherTryExample {
    public static void main(String[] args) {
        Either<String, Integer> result = divide(10, 0);

        result.peek(value -> System.out.println("Success: " + value))
            .peekLeft(error -> System.out.println("Failure: " + error));
    }

    public static Either<String, Integer> divide(int a, int b) {
        return Try.of(() -> a / b)
                .toEither()
                .mapLeft(Throwable::getMessage);
    }
}

How to Use Either in Practical Scenarios ?

File Reading Operation

Handling file I/O operations can be cumbersome with traditional error handling. Here’s how Either can simplify this process.

import io.vavr.control.Either;
import io.vavr.control.Try;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FileReadExample {
    public static void main(String[] args) {
        Either<String, String> content = readFile("example.txt");
        content.peek(System.out::println)
            .peekLeft(error -> System.err.println("Error: " + error));
    }
    
    public static Either<String, String> readFile(String path) {
        return Try.of(() -> new
String(Files.readAllBytes(Paths.get(path))))
                .toEither()
                .mapLeft(Throwable::getMessage);
    }
}

In the previous example, the readFile method attempts to read the contents of a file.
If successful, it returns the contents wrapped in a Right. If an error occurs during the
file reading process, it returns an error message wrapped in a Left.

Database Query

Database operations are another area where Either shines by providing clear success and failure paths :

import io.vavr.control.Either;
import io.vavr.control.Try;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class DatabaseQueryExample {
    public static void main(String[] args) {
        Either<String, ResultSet> result = executeQuery("SELECT *
FROM users");

        result.peek(rs -> {
                try {
                    while (rs.next()) {
                        System.out.println("User: " + rs.getString("name"));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            })
            .peekLeft(error -> System.err.println("Error: " + error));
    }

    public static Either<String, ResultSet> executeQuery(String query) {
        return Try.of(() -> {
                    Connection connection =

DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
                    Statement statement =
connection.createStatement();
                    return statement.executeQuery(query);
                })
                .toEither()
                .mapLeft(Throwable::getMessage);
    }
}

In this example, the Try.of() method attempts to execute the database query. If successful, it returns a Success containing the query result set. Otherwise, it returns a Failure containing the exception thrown during the query execution. The toEither() method converts the Try result into an Either, allowing for consistent error handling across different parts of the application.

Conclusion

Either from the Vavr library offers a powerful and unified approach to handling exceptions in Java. By encapsulating both success and failure states within a single construct, Either enables more expressive and maintainable code. Embracing Either and the principles of immutability can lead to cleaner, more reliable Java applications, transforming how you handle errors and improving overall code quality.