A story of Checked Exceptions and Java 8 Lambda Expressions

lambda-throws-150Image 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 more safe, 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 waymadhat

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. Unfortunately 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 such 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 after some point.

Generics to the rescue?

Let’s try to explore a 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 probable 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 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 Labyrinthlabyrinth

This all seem 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 on 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 the we developers have, at least until a proper language feature is introduced to correctly deal with this issue, or checked exceptions become completely deprecated.

Advertisements

8 thoughts on “A story of Checked Exceptions and Java 8 Lambda Expressions

  1. It leaves me unhappy, but my workaround for this has been wrapping and unwrapping:

     
        public static  void process(
                final ThrowingConsumer consumer,
                final Consumer<Consumer> process)
                throws E {
            try {
                process.accept(consumer.asConsumer());
            } catch (final WrappedException e) {
                throw (E) e.getCause();
            }
        }
    
        public static final class WrappedException
                extends RuntimeException {
            WrappedException(final Exception cause) {
                super(cause);
            }
        }
    
        @FunctionalInterface
        public interface ThrowingConsumer {
            void accept(final T t)
                    throws E;
    
            default Consumer asConsumer() {
                return t -> {
                    try {
                        accept(t);
                    } catch (final RuntimeException e) {
                        throw e;
                    } catch (final Exception e) {
                        throw new WrappedException(e);
                    }
                };
            }
        }
    

    Like

  2. Previous comment left out example use (also I swapped arg order in `process`):

        public static void main(final String... args)
                throws Exception {
            final Stream stream = Stream.of("a", "b", "c").
                    distinct();
    
            process(stream::forEach, t -> {
                switch (t) {
                case "b":
                    throw new Exception("Sad!");
                default:
                    System.out.println(t);
                }
            });
        }
    

    Like

    1. Cool idea thanks, I definitely like the syntax more than what I proposed.

      However, you are a bit cheating 🙂 You pre-define that a TransactionDataSource should always throw IOException and SQLException. The logic can not throw anything else this way, it must throw one of those two.

      Imagine though that most business-code would, I expect, be inside one of these constructs, so we can not constrain it to any exception types preemptively. Real exception transparency would require that we throw exactly what the logic throws, without prior knowledge of what the code is for.

      Like

      1. That’s the implementation detail. You can implement as many TransactionalDataSource-like classes as you need. I’m afraid that’s all I can squeeze out of the Java type system.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s