Handling exceptions in Java: A use case from test automation framework

Photo by Dim Hou on Unsplash

Handling exceptions in Java: A use case from test automation framework

Context

As a Test automation engineer, we write lots of code for various purposes.
Exception handling is one of the crucial parts of writing better code.

One of the qualities of a well-written code is the way we handle errors and exceptions that occur during our execution. We all know, how much time we waste to debug an issue that results due to an error or exception.

One way is to have a mechanism that will isolate and communicate the exact error when it occurs. So that we can find and fix them easily and promptly.

That is where the exception-handling concept comes into the picture.

Use case

Let's assume we need to read a property file that contains some values like, browser-name, environment-name, report-path and so on.

We write code to read the file and then fetch the values of the above properties.

The code will look something like this ( Without proper handling ):

public static Properties getConfigProperty(String filePath) throws IOException {
    Properties properties = new Properties();

    FileReader fileReader = new FileReader(filePath); // throws FileNotFoundException
    BufferedReader reader = new BufferedReader(fileReader);
    properties.load(reader); // throws IOException

    return properties;
  }

If you notice the code, the method is simply throwing the exception to the called method and it will keep propagating upwards.

Why that is a problem?

  1. It will propagate the error to the caller methods till the top, which is good but what if, this exception does not need to be thrown at all and can be contained and handled here itself?

  2. There are multiple chances of exceptions being thrown, one for FileReader and another for load() , we can't exactly say when and what error might occur

Though this is a simple use case, but main point is that such exceptions might clutter the logs and make us confused and we might end up wasting time figuring out why the exception

Solution

It is always recommended to handle the exceptions effectively based on context. Let's see how we handle this.

Option 1: Propagating it to caller methods, if this is a requirement

When to apply this ?

  • Consider how exceptions are handled throughout your application. If FileNotFoundException is expected to be handled differently at a higher level of the application, it may be appropriate to rethrow it to propagate the exception up the call stack.

  • Rethrowing the original FileNotFoundException preserves its type and allows callers to handle it specifically.

  • Alternatively we can also use @SneakyThrows annotation for the method and that will not require any throws in method signature.

// FileHandler class
public static Properties getConfigProperty(String filePath) throws IOException {
    Properties properties = new Properties();

    // using try with resources to better manage closing of resources automatically
    try (
            FileReader fileReader = new FileReader(filePath); // throws FileNotFoundException
            BufferedReader reader = new BufferedReader(fileReader) // does not throw any exception but we added it to align inside resource block
    ) {
      properties.load(reader); // throws IOException
    } catch (FileNotFoundException e) { // catching lower level exception first
      log.error("Properties file not found:: {}", filePath, e); // proper way of logging error with custom message
      throw new FileNotFoundException("Properties file not found", e);
    } catch (IOException e) { // catching higher level exception later
      log.error("Error reading properties file: {}", filePath, e); // proper way of logging error with custom message
      throw new IOException("Error reading Properties file: " + filePath, e);
    }
    return properties;
  }

Option 2: Isolating and handling the exception within the method with a dedicated exception handler class ( Recommended approach )

When to apply this ?

  • If the exception can be adequately handled or recovered from within the current method or component, wrapping it in a RuntimeException may be sufficient.
// creating dedicated exception handler class
public class RuntimeExceptionHandler extends RuntimeException {
  public RuntimeExceptionHandler(String message, Throwable cause) {
    super(message, cause);
  }
}

// FileHandler class
public static Properties getConfigProperty(String filePath) {
    Properties properties = new Properties();

    // using try with resources to better manage closing of resources automatically
    try (
            FileReader fileReader = new FileReader(filePath); // throws FileNotFoundException
            BufferedReader reader = new BufferedReader(fileReader) // does not throw any exception but we added it to align inside resource block
    ) {
      properties.load(reader); // throws IOException
    } catch (FileNotFoundException e) { // catching lower level exception first
      log.error("Properties file not found:: {}", filePath, e); // proper way of logging error with custom message
      throw new RuntimeExceptionHandler("Properties file not found", e);
    } catch (IOException e) { // catching higher level exception later
      log.error("Error reading properties file: {}", filePath, e); // proper way of logging error with custom message
      throw new RuntimeExceptionHandler("Error reading Properties file: " + filePath, e);
    }
    return properties;
  }

How do you handle exceptions? do suggest in the comment if you have better ways.