I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

Sometimes try/catch blocks can result in verbose or even awkward code constructs.

In this article, we’ll focus on NoException which provides concise and handy exception handlers.

2. Maven Dependency

Let’s add the NoException to our pom.xml:

<dependency>
    <groupId>com.machinezoo.noexception</groupId>
    <artifactId>noexception</artifactId>
    <version>1.1.0</version>
</dependency>

3. Standard Exception Handling

Let’s start with a commonly-seen idiom:

private static Logger logger = LoggerFactory.getLogger(NoExceptionUnitTest.class);

@Test
public void whenStdExceptionHandling_thenCatchAndLog() {
    try {
        logger.info("Result is " + Integer.parseInt("foobar"));
    } catch (Throwable exception) {
        logger.error("Caught exception:", exception);
    }
}

We start by allocating a Logger and then entering a try block. If an Exception is thrown, we log it:

09:29:28.140 [main] ERROR c.b.n.NoExceptionUnitTest 
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4. Handling Exceptions with NoException

4.1. Default Logging Handler

Let’s replace this with NoException‘s standard exception handler:

@Test
public void whenDefaultNoException_thenCatchAndLog() {
    Exceptions 
      .log()
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

This code gives us almost the same output as above:

09:36:04.461 [main] ERROR c.m.n.Exceptions 
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

In its most basic form, NoException provides us with a way to replace try/catch/ exceptions with a single line of code. It executes the lambda that we pass to run(), and if an Exception gets thrown, it gets logged.

4.2. Adding a Custom Logger

If we look closely at the output, we see that exceptions get logged as the logging class, instead of ours.

We can fix that, by providing our logger:

@Test
public void whenDefaultNoException_thenCatchAndLogWithClassName() {
    Exceptions
      .log(logger)
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

Which gives us this output:

09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest 
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4.3. Supplying a Custom Log Message

We may want to use a different message than the default “Caught Exception.” We can do this by passing a Logger as the first argument and a String message as the second:

@Test
public void whenDefaultNoException_thenCatchAndLogWithMessage() {
    Exceptions
      .log(logger, "Something went wrong:")
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

Which gives us this output:

09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest 
  - Something went wrong:
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

But what if we want to do more than just log Exceptions, such as insert a fallback value when parseInt() fails?

4.4. Specifying a Default Value

Exceptions can return a result wrapped in an Optional. Let’s move things around so we can use it to provide a default value if the target fails:

@Test
public void
  givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
    System.out.println("Result is " + Exceptions
      .log(logger, "Something went wrong:")
      .get(() -> Integer.parseInt("foobar"))
      .orElse(-1));
}

We still see our Exception:

12:02:26.388 [main] ERROR c.b.n.NoExceptionUnitTest
  - Caught exception java.lang.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

But we also see our message printed to the console too:

Result is -1

5. Creating a Custom Logging Handler

So far we have a nice method of avoiding repetition and making code more readable in simple try/catch/log scenarios. What if we want to reuse a handler with a different behavior?

Let’s extend NoException‘s ExceptionHandler class and perform one of two things depending on the exception type:

public class CustomExceptionHandler extends ExceptionHandler {

Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);

    @Override
    public boolean handle(Throwable throwable) {
        if (throwable.getClass().isAssignableFrom(RuntimeException.class)
          || throwable.getClass().isAssignableFrom(Error.class)) {
            return false;
        } else {
            logger.error("Caught Exception", throwable);
            return true;
        }
    }
}

By returning false when we see an Error or a RuntimeException we’re telling ExceptionHandler to re-throw. By returning true for everything else, we indicate that exception has been handled.

First, we’ll run this with a standard exception:

@Test
public void givenCustomHandler_whenError_thenRethrowError() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> "foo".charAt(5));
}

We pass our function to the run() method in our custom handler inherited from ExceptionHandler:

18:35:26.374 [main] ERROR c.b.n.CustomExceptionHandler 
  - Caught Exception 
j.l.StringIndexOutOfBoundsException: String index out of range: 5
at j.l.String.charAt(String.java:658)
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:20)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:10)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
at c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:10)

This exception is logged. Let’s try with an Error:

@Test(expected = Error.class)
public void givenCustomHandler_whenException_thenCatchAndLog() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> throwError());
}

private static void throwError() {
    throw new Error("This is very bad.");
}

And we see that the Error was re-thrown into main(), rather than logged:

Exception in thread "main" java.lang.Error: This is very bad.
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:15)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:8)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
t c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:8)

So we have a reusable class that can be used across an entire project for consistent exception handling.

6. Conclusion

With NoException we can simplify the exception handling on a case-by-case basis, with a single line of code.

The code can be found in this GitHub project.

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS

Sort by:   newest | oldest | most voted
comanl
Guest

Zum Anfang der Metadaten

If you want to handle exceptions in a more functional way here are some classical examples:
http://www.vavr.io/vavr-docs/#_try
https://github.com/aol/cyclops/wiki/Try-:-functional-exception-handling-for-Java-8

Grzegorz Piwowarek
Editor

Those are kind of two different things. This library is only supposed to simplify implementing common exception handling scenarios and nothing else.

Also, we do have an article about that: http://www.baeldung.com/javaslang-try