NoException - Exception handlers for Java
NoException is functional programming for Java exception handlers. Many applications contain thousands of try-catch constructs and it's a mess. Catch clauses are verbose, repetitive, inconsistent, buggy, and hard to test. NoException provides a set of predefined exception handlers (try-catch replacements) that are concise and neat. You can define your own reusable handlers to fit policies of your project.
Example:
System.out.println( Exceptions.silence().get(() -> "test".substring(5)).orElse("fallback"));
The above is equivalent to the following try-catch code:
String result; try { result = "test".substring(5); } catch (Throwable exception) { result = "fallback"; } System.out.println(result);
There's more to NoException. Read on.
Download
Get NoException from Maven Central:
<dependency> <groupId>com.machinezoo.noexception</groupId> <artifactId>noexception</artifactId> <version>1.9.1</version> </dependency>
Or clone sources from GitHub or Bitbucket. Don't forget to configure your build for Java 11+. Last version compatible with Java 8 was 1.6.2. Sources and binaries are distributed under Apache License 2.0.
If your project is a Java module,
add the following declaration to your module-info.java
:
requires com.machinezoo.noexception;
Why?
Application code is often littered with boilerplate try-catch blocks. Verbosity of try-catch encourages developers to avoid it and to propagate exceptions instead. Notable recurring examples include fallbacks, callbacks, event/job loops, executors, and lambdas.
// Fallbacks: Exception severity escalated where log & fallback would suffice. important.add(unimportant()); // Callbacks: Exception erroneously propagated to unrelated code. if (callback != null) callback.run(); // Event/job loops: Exception kills the whole loop including unrelated jobs. for (Job job : jobs) job.run(); // Executors: Exception lost in Future that is not awaited. executor.submit(() -> Files.write("file", data)); // Lambdas: Compiler error due to checked exception. process(() -> Files.readAllLines(path));
Tutorial
NoException defines a set of reusable checked and unchecked exception handlers (or exception blocks) and lets you easily define your own. NoException's handlers are concise, lambda-friendly, and thoroughly tested. They can be used to ensure correct and consistent exception handling throughout your project.
Catch-all handlers
If you just want to get rid of all exceptions, use Exceptions.silence()
.
String test = null; Exceptions.silence().run(() -> System.out.println("This is a " + test));
If exception is thrown, Exceptions.silence()
will catch it,
discard it, and then it will return normally.
Fallbacks
To return a value, call get(Supplier)
instead of run(Runnable)
.
It returns standard Optional
,
which lets you specify fallback value via orElse(T)
or fallback supplier via
orElseGet(Supplier)
.
System.out.println( Exceptions.silence().get(() -> "test".substring(5)).orElse("fallback"));
Pass-through handlers
If you prefer the handler to rethrow the exception after processing it,
you can call passing()
method on the handler
to get a pass-through version of the same exception handler.
for (String item : List.of("three", "two", "one")) { System.out.println(ExceptionLogging .log("Failed: " + item) .passing() .get(() -> item.substring(4)))); }
Notice there's no need for fallback value here.
Checked exceptions
You can avoid checked exception nuisance, especially on lambdas, by either wrapping checked exceptions in unchecked ones using
Exceptions.wrap()
or by sneaking them past compiler checks using Exceptions.sneak()
,
which will run some perfectly legal java code that tricks the compiler to let an undeclared checked exception through.
byte[] utf = Exceptions.sneak().get(() -> "test".getBytes("UTF-8"));
The above code can still throw checked exceptions, but the compiler believes it will not.
If you want to also catch the checked exceptions, combine checked and unchecked exception handlers:
static final List<String> lines = Exceptions.silence() .get(Exceptions.sneak().supplier(() -> Files.readAllLines("test.txt"))) .orElse(Collections.emptyList());
Functional interfaces
To handle exceptions from functional interfaces with parameters, first wrap the functional interface and then apply its parameters.
Function<String, String> hello = x -> "Hello " + x; System.out.println(Exceptions.silence().function(hello).apply(null).orElse("Who?"));
Method function(Function)
above returns OptionalFunction
,
a special functional interface defined by NoException that returns
Optional
when executed.
In order to pass it to a method that expects standard
Function
,
you can call orElse(R)
method on the OptionalFunction
.
Function<Object, String> format = x -> "[" + x.toString() + "]"; Function<Object, String> safe = Exceptions.silence().function(format).orElse("???"); System.out.println(safe.apply(null));
Predefined handlers
Class Exceptions
provides several predefined exception handlers:
silence()
- Silently swallow the exception.sneak()
- Smuggle checked exceptions past compiler checks.wrap()
- Wrap checked exceptions in unchecked ones.wrap(Function<Exception, RuntimeException>)
- Like above, but with custom wrapper.propagate()
- Let the exception through (a no-op).
Class ExceptionLogging
from SLF4J extension provides additional logging handlers:
log()
- Logs the exception to SLF4J and swallows it.log(Logger)
- Like above, but with custom logger.log(Logger, String)
- Like above, but with custom message.log(Logger, Supplier<String>)
- Like above, but with lazily evaluated message.
See current extension list for complete offering of predefined exception handlers.
Custom handlers
To create custom exception handler, override
handle(Throwable)
method of ExceptionHandler
.
public class ExceptionCounter extends ExceptionHandler { private long count; @Override public synchronized boolean handle(Throwable exception) { ++count; return true; } public synchronized long report() { return count; } } public class ExceptionPolicy { private static final ExceptionCounter counter = new ExceptionCounter(); public static ExceptionCounter count() { return counter; } }
And use it anywhere:
ExceptionPolicy.count().run(() -> oftenFailingCode()); System.out.println(ExceptionPolicy.count().report());
A pass-through version can be derived by calling passing()
or you can directly implement ExceptionFilter
.
You can also define custom checked exception policy by implementing CheckedExceptionHandler
,
but you are usually better off supplying custom wrapper
to wrap(Function<Exception, RuntimeException>)
.
public class ExceptionPolicy { public static CheckedExceptionHandler wrap(String message) { return Exceptions.wrap(e -> new RuntimeException(message, e)); } } // example usage ExceptionPolicy.wrap("Failed to save").run(() -> Files.write(path, data));
Configurator
You would normally use your IDE's auto-complete. This configurator is here only to help you understand the API. It shows canonical, idiomatic code.
String result = Exceptions.silence().get(() -> /* ... */).orElse("fallback"); // multi-line example String result = Exceptions.silence().get(() -> { // ... return "result"; }).orElse("fallback");
See Exceptions.silence()
, ExceptionHandler.get(Supplier)
.
Supported functional interfaces
NoException supports all standard functional interfaces in Java.
The following table links to javadoc for every combination of functional interface and implemented feature.
Link is missing only where it does not make sense, for example when the functional interface returns void
or it is not parameterless.
Alternatives
NoException was developed shortly after Java 8 launch, but I kept it as an internal project. By the time I got around to publishing it, there were several other projects solving the same problem. I am nevertheless pleasantly surprised that NoException is a clean superset of functionality in the other projects.
NoException | fge | jOOL | StreamUnthrower | Liebenberg | Durian | Faux Pas | |
---|---|---|---|---|---|---|---|
Logging handler | yes | no | no | no | rethrowing | yes | no |
Silent handler | yes | no | no | no | yes | yes | no |
Custom handler | yes | no | yes | no | yes | yes | no |
Wrapped rethrow | yes | yes | yes | no | no | yes | no |
Sneaky rethrow | yes | yes | yes | yes | yes | no | yes |
Wraps lambda | yes | yes | yes | no | yes | yes | yes |
Runs lambda | yes | no | no | yes | no | yes | no |
Optional return | yes | custom | no | no | no | fallback only | no |
Supported interfaces | all | all | all | custom | all | few | some |
Tutorial | yes | yes | yes | yes | yes | sort of | yes |
Javadoc | yes | no | yes | no | yes | yes | no |
Maven Central | yes | yes | yes | no | yes | yes | yes |
Next steps
- Download NoException from Maven Central if you haven't done so yet.
- Check Javadoc for exact API semantics.
- Check out available extensions for additional exception handlers.
- Report an issue or submit PR on GitHub or Bitbucket if something is wrong or poorly documented.