How to Correctly Implement ‘Sneaky Throws’ in Java
A straightforward, lightweight, and useful approach to dealing with methods that throw checked exceptions with code snippets.
Join the DZone community and get the full member experience.
Join For FreeIf you ask Java developers about the concept of ‘Sneaky Throws,’ I am almost sure there will be a couple of opinions that are quite differently expressed, but similar in their meaning. Some will sum it up as being able to throw checked exceptions without declaring them explicitly; others will amend that it means writing functional-style code (lambdas) and being allowed to call methods that throw checked exceptions.
Most probably, it will be surely mentioned that there’s a Lombok annotation called exactly @SneakyThrows that solves the problem immediately when put on a method. Last but not least, to outline it in a more pragmatic manner, the concept allows tricking the Java compiler into treating checked exceptions as runtime exceptions.
All of these are valid points of view, and to clarify the concept, this article aims to provide a straightforward yet useful approach to handling methods that throw checked exceptions.
Let’s jump right in and imagine the following situation. The team is requested to enhance the currently delivered application and implement new functionalities. This obviously happens on a ‘sprint-ly’ basis. Nevertheless, the project has been successfully developed for quite a while now; it also deals with legacy code, and moreover, developers are interacting with other parts of code that were written, let’s say, in a less fortunate manner. Such an example is the class below.
public class TwoDigitsInteger {
private final Integer value;
public TwoDigitsInteger(Integer value) {
this.value = value;
}
public boolean isValid() throws NotSetException {
if (value == null) {
throw new NotSetException("Number value not set.");
}
return value >= 10 && value <= 99;
}
public Integer getValue() throws NotSetException {
if (value == null) {
throw new NotSetException("Number value not set.");
}
return value;
}
}
Just as its name suggests, it models a two-digit integer number. Instances of this class are immutable; the value is set upon construction, and it declares two methods, one for reading the value — getValue() — and another one for validating it — isValid(). We’re not going to further elaborate on the quality of the code, as it helps in the experiment done. The main issue here, the plot of this article, is the fact that both methods declare a NotSetException as they might throw it under certain circumstances, and even that might be fine unless this Exception hadn’t been a checked one.
public class NotSetException extends Exception {
public NotSetException(String message) {
super(message);
}
}
One option (and definitely the one worth taking into account) is to profit and consider the moment a good opportunity to refactor this ‘legacy’ code and at least make the Exception a runtime one. A few unit tests can be written (in case these are missing), then the implementation improved, and focus can be moved on the newly requested features.
Nevertheless, for the sake of the experiment in this article, it’s assumed the TwoDigitsInteger class is kept as it currently is and the Exception remains checked.
Exception Function
Let’s consider a very simple scenario: there is a collection of TwoDigitsIntegers and the intent is to create a string expression that outlines the sum of the numbers.
List<TwoDigitsInteger> numbers = List.of(new TwoDigitsInteger(10),
new TwoDigitsInteger(25),
new TwoDigitsInteger(37));
If writing the code as in the test below,
@Test
void sumExpression() {
String result = numbers.stream()
.map(TwoDigitsInteger::getValue)
.map(String::valueOf)
.collect(Collectors.joining("+"));
Assertions.assertEquals("10+25+37", result);
}
the Java compiler will complain, saying — Unhandled exception: com.hcd.utilities.NotSetException – as the getValue() method declares a checked Exception and obviously it cannot be used inside a stream.
To solve the issue, a try-catch is needed, which makes the code quite difficult to read (and ugly). Not to mention that we’re modifying the state of the joiner as we loop the collection.
@Test
void sumExpression1() {
StringJoiner joiner = new StringJoiner("+");
for (TwoDigitsInteger number : numbers) {
try {
joiner.add(String.valueOf(number.getValue()));
} catch (NotSetException e) {
throw new RuntimeException(e);
}
}
String result = joiner.toString();
Assertions.assertEquals("10+25+37", result);
}
In order to overcome this and allow having a fluent API even in situations where checked Exceptions are present, the following ExceptionFunction interface is created.
@FunctionalInterface
public interface ExceptionFunction<T, R, E extends Exception> {
R apply(T t) throws E;
}
It is general enough; it represents a function that accepts one argument (of type T), produces a result (of type R) and when applied, an Exception subclass (of type E) might be thrown. Implementers shall define a single method, which effectively applies the function.
Additionally, the following class is defined.
public final class ExceptionWrapper {
public static <T, R, E extends Exception> Function<T, R> apply(ExceptionFunction<T, R, E> function) {
return t -> {
try {
return function.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
ExceptionWrapper() {
throw new UnsupportedOperationException("No need to be called.");
}
}
When the ExceptionWrapper#apply() method is called, in case an Exception is thrown, it is wrapped into a RuntimeException one and thrown further irrespective of the type of the initial one (the checked Exception case is obviously covered as well, so we’re good).
The ExceptionFunction passed as a parameter represents the initial call that is wrapped to overcome the problem.
The previously discussed test is modified to use the ExceptionWrapper#apply() method. Not only does it now compile and run successfully, but the code readability is definitely improved.
@Test
void sumExpression() {
String result = numbers.stream()
.map(ExceptionWrapper.apply(TwoDigitsInteger::getValue))
.map(String::valueOf)
.collect(Collectors.joining("+"));
Assertions.assertEquals("10+25+37", result);
}
Exception Predicate
Let’s now consider another straightforward scenario, one in which we want to count only the valid two-digit integers that are found in a designated range. Also, for the sake of this experiment, it’s assumed the previous TwoDigitsInteger class is used.
As in the previous case, the following piece of code that would do the job doesn’t compile because of the same reason – Unhandled exception: com.hcd.utilities.NotSetException — as the isValid() method declares a checked exception, and it cannot be used inside a stream.
long count = IntStream.range(0, 150)
.mapToObj(TwoDigitsInteger::new)
.filter(TwoDigitsInteger::isValid)
.count();
Again, assuming the TwoDigitsInteger is needed, one would have to loop through the numbers, check them in a try-catch for checked NotSetExceptions as isValid() declares it, then pack the Exception as a RuntimeException one and throw it further, finally count the valid number. This is already way too complicated even when only enumerating the steps in natural language.
To be able to keep the API fluid and use streams when performing checks that declare checked Exception, the next interface is declared.
@FunctionalInterface
public interface ExceptionPredicate<T, E extends Exception> {
boolean test(T t) throws E;
}
It represents a predicate (a boolean-valued function) of one argument that might throw an Exception subclass. The method evaluates the predicate on the given argument and returns true if the input argument matches, or false otherwise.
In addition, the following method is added to the ExceptionWrapper class, very similar to the apply() one.
public static <T, E extends Exception> Predicate<T> test(ExceptionPredicate<T, E> predicate) {
return t -> {
try {
return predicate.test(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
When called, it effectively applies the provided predicate. In case an Exception is thrown, it is wrapped into a RuntimeException one and thrown further.
The initial code can now be rewritten as below and successfully compiled and executed.
@Test
void count() {
long count = IntStream.range(0, 150)
.mapToObj(TwoDigitsInteger::new)
.filter(ExceptionWrapper.test(TwoDigitsInteger::isValid))
.count();
Assertions.assertEquals(90, count);
}
Takeaways
Although simple and to-the-point, the presented solution comes in very handy, especially when dealing with functions that declare checked Exceptions and are further used in the code that we produce. For sure, other ready-to-use alternatives already exist, an example being the Lombok @SneakyThrows annotation. Personally, I have very rarely included the Lombok library in any of my projects and as Java introduced the records, this becomes even more unlikely to happen in the future. That being said, the structures described in this article are very helpful, lightweight, and easy to understand and use when needed.
ExceptionWrapper, ExceptionFunction and ExceptionPredicate source code is part of the asentinel-orm open-source project. To use it, one may either declare the Maven dependency in their pom.xml file (version 1.72.2 is the latest at the moment of this writing)
<dependency>
<groupId>com.asentinel.common</groupId>
<artifactId>asentinel-common</artifactId>
<version>1.72.2</version>
</dependency>
or use it directly if considering there’s too much overhead to include the whole library.
Resources
[1] – asentinel-orm open-source ORM project is here
[2] – the picture was taken at ‘Harry Potter Warner Bros. Studios’, near London
Published at DZone with permission of Horatiu Dan. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments