Skip to content

Handling exceptions with try-catch and Vavr Try

Hello! Let me this time to speak about one very important topic – exception handling in Java. This subject may seem discussed many times, however not all articles that discuss this question are really useful and relevant. An exception handling mechanism in Java is commonly associated with try-catch block, that we use in order to catch an exception and to provide some logic that would be executed in a case, when an exception occurs. Also, it is true, that don’t need to put all exceptions inside these blocks. From another hand, if you are working on software design of your application, built-in exception handling mechanism may not be 100% what you want. In such situation you can try to use an alternative – Vavr Try construction. Last time we have already started a discussion on using Vavr in Java apps in regard of null value check.

In this post we would explore the Java exception handling approach and would also discuss how to use Vavr Try as a substitute to built-in methods.

Handle 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 on 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){

}

NB we could refactor it to try-with-resources, but we would talk about that a bit later; for now this code is ok. So, why do we have to write this code like this? Because SQLException is a checked exception. You have to declare these exceptions in a method or constructor’s throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary. Methods, that we use in our example throw SQLException, if a database access error occurs. Therefore we surround it with try-catch block.

Java validates these exceptions during compilation – and that what makes them different from runtime exceptions (would talk a bit later about them).

But you don’t have to handle all exceptions

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

Case 1. Runtime exceptions

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

Graph 1. Hierarchy of Java exceptions

You can note, that runtime exceptions are a specific group. According to Java specification, a recovery from these exceptions are may still be possible. As example, let recall ArrayIndexOutOfBoundsException. Have a look on a sample code snippet:

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 definetly out of range (index= 6), Java would thrown ArrayIndexOutOfBoundsException. This indicates that index we try to call is either negative or 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 such code…

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 a look on the mentioned graph again – errors are there, but they are usually not handled. Why? Often, this 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 think about the memory error – java.lang.VirtualMachineError. This error indicates that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating. Or in other words, an app goes 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 call JVM to do it). And even such handler may be useful in this context, such attempt might not be successful.

Variants of try-catch block

The mentioned way of writing try-catch statement is not 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 in these groups.

Variant 1. Try-with-resources

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 as resource any class that implements 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 code will be executed. Before this construction, we had to close resources inside finally block explicitly.

Variant 2. Try + finally

Finally block is executed in any case, e.g. in case of success and in case of exception. Inside it you need to put a 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) {
       		//...
       }
    }
}

You can note that there is a drawback in this approach: if exception is thrown inside finally block, it makes it interrupted. Therefore we have to handle exception as normal. NB: use try-with-resources with closeable resources and avoid closing of resources inside finally block.

Variant 3. Multiple catches

At the end, Java permits us to catch several times of exceptions at one try-catch block. This is useful, when methods throw several type of exceptions and you would like to differentiate a logic for each case. As example, let take this fictionous class that has methods that declare to 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 that exceptions are at same level. But you have to order catch blocks from most specific to most general, let say, catch for ArithmeticException must come before catch for Exception.

So, we reviewed how to handle exceptions in Java using built-in approach. Now, let have a look how to do it with Vavr library.

Vavr Try

We reviewed a standard way of catching Java exceptions. Another approach is to use Vavr Try class. First, add 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 a form of Success or Failure. Take a look on 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 code we will use try-catch blocks to handle DatabaseAccessException. But other solution is to refactor it with Vavr. Check 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 have have 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 “problematic” method, for example with find.

Handling failure

In other 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 error:

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

That means, that because we hardcoded a failure in 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 like 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 a 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 more functional way. No doubts, that its 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 try-catch block, that we use in order to catch an exception and to provide some logic that would be executed in a case, when an exception occurs. Also, it is true, that don’t need to put all exceptions inside these blocks. In this post we explored how to

References

  • Jeff Friesen Are checked exceptions good or bad? (2016) JavaWorld, read here
  • Rajasekar Elango Better Exception Handling in Java 8 Streams Using Vavr (2018) Freecodecamp, read here
  • Stéphane Derosiaux Using vavr to write more robust Java code (2018) Medium, read here