Handling Exceptions in Java With Try-Catch Block and Vavr Try

Exceptions in Java

Learn more about handling exceptions!

Today, we are going to talk about a very important topic — exception handling in Java. While this subject may seem overly discussed at times, not every article out there contains useful and relevant information.

The most common exception handling mechanism in Java is often associated with the try-catch block. We use this to catch an exception and then provide logic that would then be executed in the case where an exception occurs.

You may also like: Using Java Optional Vs. Vavr Option

It's also true that you don’t need to put all exceptions inside these blocks. On another hand, if you are working on the software design of your application, a built-in exception handling mechanism may not be what you want. In such situations, you can try to use an alternative – the Vavr Try construction. Last time, we had a discussion on using Vavr in Java apps in regards to the null value check.

In this post, we are going to explore different approaches to Java exception handling and discuss how to use Vavr Try as a substitute for built-in methods. Let's get started!

Handling Exceptions in Java

As introduction let review how Java permits us to handle exceptions. If you don’t remember it, the exception in Java states for unwanted or unexpected event, which occurs during the execution of a program i.e at run time, that disrupts the normal flow of the program’s instructions. Java offers us the aforesaid try-catch mechanism to catch exceptions. Let briefly check how does it work.

What Happens if You Don’t Handle Exceptions?

Ok, let take a look at a very common example. Here is a code snippet where we have a JDBC code. Take a look:

Connection connection = dataSource.getConnection();
String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?";
PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);


Frankly speaking, your IDE does not permit you even to write this code and requires you to surround it with try-catch block. Like this:

try {
Connection connection = dataSource.getConnection();
String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?";
PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);
} catch (SQLException ex){

}


Please note: We could refactor it to try-with-resources, but we will talk about that a bit later. For now, this code will do.

So, why do we have to write this code like this? Because SQLException is a checked exception.

You have to declare these exceptions in either a method or a constructor’s throws clause if they can be thrown by the execution of the method or the constructor and propagate outside of the method or constructor boundary. Methods that we use in our example throw a SQLException if a database access error occurs. Therefore, we surround it with a try-catch block.

Java validates these exceptions during compilation — and that's what makes them different from run-time exceptions (we will talk about these a bit later).

But You Don’t Have to Handle All Exceptions...

However, not every exception should be surrounded by a try-catch block.

Case 1. Runtime Exceptions

Java exceptions are subclasses of Throwable, but some of them are subclassed from the RuntimeException class. Take a look at the graph below that presents a hierarchy of Java exceptions:

Hierarchy of Java exceptions

Please note that runtime exceptions are a specific group. According to Java specification, a recovery from these exceptions may still be possible. As an example, let's recall ArrayIndexOutOfBoundsException. Have a look at the sample code snippet below:

int numbers[] = [1,43,51,0,9];
System.out.println(numbers[6]);


Here, we have an array of integers with 5 values (0-4 position). When we try to retrieve a value that is definitely out of range (index= 6), Java will throw an ArrayIndexOutOfBoundsException.

This indicates that the index we try to call is either negative, greater than, or equal to the size of the array. As I said, these exceptions can be recovered, so they are not checked during compilation. That means you can still write code such as:

int numbers[] = [1,43,51,0,9];
int index = 6;
try{
System.out.println(numbers[index]);
} catch (ArrayIndexOutOfBoundsException ex){
System.out.println("Incorrect index!");
}


…but you are not required to do so.

Case 2. Errors

Error is another tricky concept. Take another look at the graph above – errors are there, but they are usually not handled. Why? Often, this is due to the fact that there is nothing that a Java program can do to recover from the error, e.g. errors indicate serious problems that a reasonable application should not even try to catch.

Let's think about the memory error – java.lang.VirtualMachineError. This error indicates that the JVM is broken or has run out of resources necessary for it to continue operating. Or, in other words, if an app runs out of memory, it simply can’t allocate additional memory resources.

Certainly, if a failure is a result of holding a lot of memory that should be made free, an exception handler could attempt to free it (not directly itself but it can call the JVM to do it). And while such a handler may be useful in this context, such an attempt may not be successful.

Variants of the Try-Catch Block

The above-mentioned way of writing a try-catch statement is not the only one that is available in Java. There are other methods: try-with-resources, try-catch-finally, and multiple catch blocks. Let make a quick trip to these different groups.

Variant 1. Try-With-Resources

The Try-with-resources block was introduced in Java 7 and allows developers to declare resources that must be closed after the program is finished with it. We can include a resource in any class that implements the AutoCloseable interface (that is a specific marker interface). We can refactor the mentioned JDBC code like this:

try (Connection connection = dataSource.getConnection){
String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?";
PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);
} catch (SQLException ex){
//..
}


Java ensures us that Connection will be closed after the code is executed. Before this construction, we had to close resources inside the finally block explicitly.

Variant 2. Try + Finally

The finally block is executed in any case, e.g. in the case of success or in the case of an exception. Inside it, you need to put code that would be executed after:

FileReader reader = null;
try {
 reader = new FileReader("/text.txt");
 int i=0;
    while(i != -1){
          i = reader.read();
          System.out.println((char) i );
    }
} catch(IOException ex1){
//...
} finally{
if(reader != null){
       try {
         reader.close();
       } catch (IOException ex2) {
       //...
       }
    }
}


Please note that there is a drawback to this approach: If an exception is thrown inside the finally block, it makes it interrupted. Therefore, we have to handle the exception as normal. Use try-with-resources with closeable resources and avoid the closing of resources inside the finally block.

Variant 3. Multiple Catches

In the end, Java permits us to catch exceptions multiple times with one try-catch block. This is useful when methods throw several types of exceptions and you would like to differentiate a logic for each case. As an example, let take this fictitious class with methods that throw several exceptions:

class Repository{

void insert(Car car) throws DatabaseAccessException, InvalidInputException {
//...
}
}

//...

try {
repository.insert(car);
} catch (DatabaseAccessException dae){
System.out.println("Database is down!");
} catch (InvalidInputException iie){
System.out.println("Invalid format of car!");
}


What do you need to remember here? Generally, we assume, in this code, those exceptions are at the same level. But you have to order catch blocks from the most specific to the most general. For example, say the catch for ArithmeticException must come before the catch for Exception. Something like that.

In this section, we reviewed how to handle exceptions in Java using a built-in approach. Now, let's have a look at how to do it with the Vavr library.

Vavr Try

We reviewed a standard way of catching Java exceptions. Another approach is to use the Vavr Try class. First, add the Vavr library dependency:

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


Try Container

Vavr includes Try class that is a monadic container type, which represents a computation that may either result in an exception, or return a successfully computed value. This result can be in the form of Success or Failure. Take a look at this code:

class CarsRepository{

Car insert(Car car) throws DatabaseAccessException {
//...
}

Car find (String id) throws DatabaseAccessException {
//..
}

void update (Car car) throws DatabaseAccessException {
//..
}

void remove (String id) throws DatabaseAccessException {
//..
}
}


In calling this code, we will use the try-catch blocks to handle DatabaseAccessException. But another solution is to refactor it with Vavr. Check out this code snippet:

class CarsVavrRepository{

Try<Car> insert(Car car) {
        System.out.println("Insert a car...");
        return Try.success(car);
    }

    Try<Car> find (String id) {
        System.out.println("Finding a car...");
        System.out.println("..something wrong with database!");
        return Try.failure(new DatabaseAccessException());
    }

    Try<Car> update (Car car) {
        System.out.println("Updating a car...");
        return Try.success(car);
    }

    Try<Void> remove (String id) {
        System.out.println("Removing a car...");
        System.out.println("..something wrong with database!");
        return Try.failure(new DatabaseAccessException());
    }

}


Now, we can handle database problems with Vavr.

Handling Success

When we receive a successfully computed result, we receive a Success:

@Test
void successTest(){
    CarsVavrRepository repository = new CarsVavrRepository();
    Car skoda = new Car("skoda", "9T4 4242", "black");
    Car result = repository.insert(skoda).getOrElse(new Car("volkswagen", "3E2 1222", "red"));
    Assertions.assertEquals(skoda.getColor(), result.getColor());
    Assertions.assertEquals(skoda.getId(), result.getId());
}


Note that  Vavr.Try, as Vavr.Option, offers us a handy getOrElse method where we put a default value in case of failure. We can use this logic with a “problematic” method, for example with find.

Handling Failure

In another case, we would handle a Failure:

@Test
void failureTest(){
    CarsVavrRepository repository = new CarsVavrRepository();
        // desired car
    Car bmw = new Car("bmw", "4A1 2019", "white");
        // failure car
    Car failureCar = new Car("seat", "1A1 3112", "yellow");

    Car result = repository.find("4A1 2019").getOrElse(failureCar);
    Assertions.assertEquals(bmw.getColor(), result.getColor());
    Assertions.assertEquals(bmw.getId(), result.getId());
}


Run this code. This test will fail because of assertions the error:

org.opentest4j.AssertionFailedError: 
Expected :white
Actual   :yellow


That means that because we hardcoded a failure in the find method, we receive a default value. Instead of returning a default value, we can do other things with a result in case of error. You can chain functions with Option that makes your code much more functional:

repository.insert(bmw).andThen(car -> {
System.out.println("Car is found "+car.getId());
}).andFinally(()->{
System.out.println("Finishing");
});


Alternatively, you can execute code with the received exception, like this:

repository.find("1A9 4312").orElseRun(error->{
//...
});


Generally speaking, Vavr Try is a feature-rich solution that you can use in order to transform your codebase in a more functional way. No doubt it's real power is unleashed in combination with other Vavr classes, like Option or Collections. But anyhow, even without them, Vavr Try is a real alternative for Java try-catch blocks if you want to write more functional-style code.

Conclusion

An exception handling mechanism in Java is commonly associated with the try-catch block that we use in order to catch an exception and to provide logic that would be executed when an exception occurs. Also, it is true that we don’t need to put all exceptions inside these blocks. In this post, we explored how to do so using the Vavr library.

I hope you enjoyed!

Further Reading

Using Java Optional Vs. Vavr Option

9 Best Practices to Handle Exceptions in Java

 

 

 

 

Top