Partner – Microsoft – NPI (cat=Java)
announcement - icon

Microsoft JDConf 2024 conference is getting closer, on March 27th and 28th. Simply put, it's a free virtual event to learn about the newest developments in Java, Cloud, and AI.

Josh Long and Mark Heckler are kicking things off in the keynote, so it's definitely going to be both highly useful and quite practical.

This year’s theme is focused on developer productivity and how these technologies transform how we work, build, integrate, and modernize applications.

For the full conference agenda and speaker lineup, you can explore JDConf.com:

>> RSVP Now

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.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!