Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A Story of Checked Exceptions and Java 8 Lambda Expressions

DZone's Guide to

A Story of Checked Exceptions and Java 8 Lambda Expressions

Learn more about checked exceptions and Java 8 lambdas, with a look at transactions and transaction management.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

Image you are writing a small, awesome transaction management library to be used in a project you are working on. You want to provide an easy and safe way for your colleagues to get an SQL connection from the current transaction, use it, and then close it when the processing is done. The very first draft could very well look like this:

public interface Transaction {
   Connection open();
}

As the Connection implements AutoCloseable, the intended usage of the Transaction object is a “try-with-resources” construct:

try (Connection connection = transaction.open()) {
   ...work with connection...
}

This guarantees that connections are always closed. Job well done, right? Well…, if the developer remembers to use connections exactly this way, always, even if sometimes it would be convenient to do otherwise, even next year, and the year afterwards, and actually not just current developers but all developers who might later join the team, and so on. On second thought, maybe we could still improve the API of this awesome library to make it safer, less prone to misuse?

Starting Down the Rabbit Hole…

The new Java 8 features may hold some new ideas how to approach this problem. The streams API, for example, builds on the idea, that developers do not have to write loops to iterate a list anymore, rather the logic how to process items of a list need to be passed into the list (or its stream) itself.

So applying that strategy to the Transaction object, we might want the users of this API to pass in the logic to work with the connection, instead if “leaking” the connection out to the user:

import java.util.function.Consumer;

public interface Transaction {
   void open(Consumer<Connection> logic);
}

The Consumer is a standard Java 8 class that only has one method “accept()“, and returns nothing. The intended usage then does not include a “try-with-resources” construct, instead it involves passing the logic into the transaction:

transaction.open(connection -> {
   ...work with connection...
});

The Transaction class could be then implemented to contain the connection closing part:

public class DataSourceTransaction implements Transaction {
   @Override
   public void open(Consumer<Connection> logic) {
      try (Connection connection = dataSource.getConnection()) {
         logic.accept(connection);
      }
   }
}

The result is, that you can centralize the safety aspect of closing the connection independently of what the logic is, independently of how the connection will be used. This way developers don’t have to remember anything.

At this point, you might give yourself a pat on the back for making the world a better place, with your new, shiny library. Well…, until you try to compile it.

Exceptions Along the Way

Unfortunately, the library, as shown above, won’t compile, because “getConnection()” throws “SQLException“, and that means “open()” also needs to throw it. But, thinking about exceptions, isn’t it the case that whatever logic tries to work with the provided connection also sometimes need to throw “SQLException“? Yes, of course. Creating and executing statements on the connection certainly throws “SQLException," so it must be declared somehow for the logic too. UUnfortunately, the Consumer is defined this way:

public interface Consumer<T> {
   void accept(T t);
}

There are no exceptions there, so basically this interface is not up to the task, you have to write a new one like this:

public interface SQLConsumer<T> {
   void accept(T t) throws SQLException;
}

That would work in this case, but there may be other cases where you would perhaps like to allow for different exception types, so let’s make the exception type generic:

public interface ThrowingConsumer<T, E extends Exception> {
   void accept(T t) throws E;
}

Is that actually allowed? Well, it turns out it is, you can define generic exceptions, and the Java 8 compiler will even try to infer the exception type from the code if it can. This means the transaction can be modified to:

public interface Transaction {
   void open(ThrowingConsumer<Connection, SQLException> logic) throws SQLException;
}

Yay, the code compiles! Everything still works, although you have made a small compromise of not using the standard classes, but this is a small price to pay for the coolest API ever.

Other People’s Exceptions

Everything seems to work fine until somebody actually tries to use the library. Your colleague just made a small utility that reads lines from a file and puts them into the database. The problem is, it can not be compiled because the logic she implemented throws “IOException” in addition to “SQLException."

This is bad. Because your class is now responsible for running any fool’s code that can throw basically any exception, that would mean that the transaction’s “open()” method could actually throw any kind of exception depending on what logic is given.

It can even get worse than that! What if the logic throws multiple different exceptions? What if it throws IOException, ClassNotFoundException, or a bunch of application specific ones? At this point, there may be multiple strategies to deal with this problem:

  1. Just declare everything to throw Exception, the base class of all exceptions, this way, everything can throw anything.
  2. Catch everything and rethrow as RuntimeException, basically making everything an unchecked exception.
  3. Try to stick with generics a little bit further, and declare multiple exceptions, not just one.

The first “solution” obviously does not really solve the problem, and sooner or later makes everything declare Exception in the “throws” clause. This arguably defeats the purpose of checked exceptions.

The second solution also defeats the purpose of checked exceptions, but in exactly the opposite way: Nothing (or at least very few methods) would declare checked exceptions because everything would tend to be unchecked at some point.

Generics to the Rescue?

Let’s try to explore the third solution for now. If one exception is not enough, maybe multiple exceptions will do? What if ThrowingConsumer would declare 5 generic exceptions, instead of one?

public interface ThrowingConsumer<T, E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception, E5 extends Exception> {
   void accept(T t) throws E1, E2, E3, E4, E5;
}

You are probably asking: why 5? Well, “five exceptions should be enough for everybody,” I say to that, and you can quote me on that one, I’m pretty sure it will never come back to haunt me.

So now, the transaction’s open() method just needs to take these generic exception types, and rethrow them:

public interface Transaction {
   <E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception, E5 extends Exception>
   void open(ThrowingConsumer<Connection, E1, E2, E3, E4, E5> logic) throws SQLException, E1, E2, E3, E4, E5;
}

Your only hope at this point is that the following code will somehow magically work:

transaction.open(connection -> {
   if (1==2) {
      throw new IOException("test");
   }
   if (1==3) {
      throw new SQLException("test");
   }
});

In turns out, that this does not work as expected. The Java 8 type inference is not that clever to know that the signature of the lambda expression could take 2 different exception types. Instead, it infers that the lambda expression should throw Exception, the greatest common superclass of both exception types. Too bad.

The only way left to make this work is to “help” the type system by not leaving this decision up to the type inference engine, but to the developer to specify the exact type of exceptions to throw. This could be done with helper methods:

// Method to construct a logic without exceptions
public <T>
       ThrowingConsumer<T, VoidException, VoidException, VoidException, VoidException, VoidException>
       throwing(ThrowingConsumer<T, VoidException, VoidException, VoidException,  VoidException, VoidException> logic) {
          return logic;
}
// Construct with 1 exception
public <T, E1 extends Exception>
       ThrowingConsumer<T, E1, VoidException, VoidException, VoidException, VoidException>
       throwing(
          Class<E1> e1Class,
          ThrowingConsumer<T, E1, VoidException, VoidException, VoidException, VoidException> logic) {
             return logic;
}
// Construct with 2 exceptions
public <T, E1 extends Exception, E2 extends Exception>
       ThrowingConsumer<T, E1, E2, VoidException, VoidException, VoidException>
       throwing(
          Class<E1> e1Class, Class<E2> e2Class,
          ThrowingConsumer<T, E1, E2, VoidException, VoidException, VoidException> logic) {
             return logic;
}
// And so on for all 5 exceptions

If you still have your sanity, you might try implementing the previous example with these helper methods:

transaction.open(throwing(IOException.class, SQLException.class, connection -> {
   if (1==2) {
      throw new IOException("test");
   }
   if (1==3) {
      throw new SQLException("test");
   }
}));

Yes, it works! The compiler says that the open() method actually throws both exact exception classes, not the Exception superclass. This is a breakthrough! Maybe sanity is also a small price to pay? Anyway, your colleagues will love this for sure!

The Void of Exceptions

Since we declare all 5 exceptions as generic parameters in the ThrowingConsumer, there is a small problem of how to express if actually only one exception is thrown for example. The other generic parameters still need to be supplied, so maybe they could be RuntimeExceptions. Those are by definition unchecked exceptions, so they don’t have to be declared at the caller site. There is, however, a small problem that the intention gets lost in the noise. The intention being not to throw any additional exceptions.

This situation is similar to return values from methods. If a method does not return anything, we can say its return type is “void“. We know that void does not have any instances, so the intention of not returning anything is very clear. Well, the same can be done for exceptions too:

public class VoidException extends RuntimeException {
   private VoidException() {
   }
}

That’s it! It is a RuntimeException, so it does not need to be declared, and it has a private constructor, so it can not be instantiated at all. Which means this exception can never be actually thrown.

This is why the ThrowingConsumer declares all “unused” exception parameters as VoidException.

Lost in the Labyrinth

This all seems well and good, provided you are ready to surrender your sanity and don’t mind your colleagues cursing your name. But unfortunately, there are still technical problems with this solution, which becomes apparent if you try to extend the library a little bit.

What if a use-case comes up to be able to put a “finally” block into the transaction? Basically, a developer wants to supply a “main” logic, and another logic, that will always be executed at the end, but still within the same transaction? You would have to define a new method, which takes 2 logic arguments this way:

<E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception, E5 extends Exception,
 F1 extends Exception, F2 extends Exception, F3 extends Exception, F4 extends Exception, F5 extends Exception>
void open(
   ThrowingConsumer<Connection, E1, E2, E3, E4, E5> logic
   ThrowingConsumer<Connection, F1, F2, F3, F4, F5> finallyLogic
) throws E1, E2, E3, E4, E5, F1, F2, F3, F4, F5;

Although that looks horrible, it actually works for this specific case. The problem is that the open() method now has to declare 10 exceptions, 5 for each of the two logic parameters, of which we don’t know which ones are used. So any usages of this method can not be inside another such construct, for example in the case of an embedded transaction. In other words, this solution resists (function) composition, therefore ultimately becomes useless.

This is the point where a good player needs to fold. The root of the problem is, that exceptions behave differently than other type parameters. They somewhat resemble varargs, but with each position potentially having a different type. There is, unfortunately, no way to properly handle this characteristic with the current generics syntax.

Escape/Conclusion

It seems that there is no practical way to actually use checked exceptions together with lambda expressions consistently. This is the unfortunate state of affairs at the moment, whether it is a deliberate omission from the Java language architects or a simple oversight, does not really matter.

There are two practical possibilities to deal with checked exceptions, either throw the superclass “Exception” and let implementations define a smaller subset if they can, or just make everything an unchecked exception. Even the Java SDK is not consistent with this decision, some classes like AutoCloseable take the former solution, while others, like most of the functional interfaces, take the latter approach.

These are therefore the same options we developers have, at least until a proper language feature is introduced to correctly deal with this issue, or checked exceptions become completely deprecated.

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
lambda expressions ,java 8 ,java

Published at DZone with permission of Robert Brautigam. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}