Sunteți pe pagina 1din 15

The New Java Developer

Reforming Java Development one Post at a time…

☰  Menu

A story of Checked Exceptions and Java 8
Lambda Expressions

 February 22, 2016  9 Minutes
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 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
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 beퟙ�er 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
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
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 liퟙ�le 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
   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 preퟙ�y 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 Ex
   void open(ThrowingConsumer<Connection, E1, E2, E3, E4, E5> logic) throws SQLExce

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, VoidExcepti
       throwing(ThrowingConsumer<T, VoidException, VoidException, VoidException,  V
          return logic; 

// Construct with 1 exception 
public <T, E1 extends Exception> 
       ThrowingConsumer<T, E1, VoidException, VoidException, VoidException, VoidExc
       throwing( 
          Class<E1> e1Class, 
          ThrowingConsumer<T, E1, VoidException, VoidException, VoidException, Void
             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> 
             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 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 liퟙ�le bit.
What if a use‑case comes up to be able to put a “finally” block into the
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 Ex
   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 maퟙ�er.

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
laퟙ�er 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

Published by javadevguy

View all posts by javadevguy

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

Brian Oxley says:
March 2, 2016 at 12:24 pm
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); 
                } 
            }; 
        } 
    } 

Reply
javadevguy says:
March 2, 2016 at 1:11 pm
Thanks for the code. I guess we don’t really have a choice but to implement workarounds,
which is kind of my point. We shouldn’t have to, should we?

Reply
Brian Oxley says:
March 2, 2016 at 12:28 pm
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); 
            } 
        }); 
    } 

Reply
David Clark says:
March 2, 2016 at 3:48 pm
Adding exception handling to lambda expressions turns the resultant code into a messy
mishmash of before/after java 8 styles and makes me sad

Reply
Paweł Prażak says:
March 3, 2016 at 7:28 pm
Looks like it can be done, with a lots of Scala‑like code:

using(dataSource).with( 
connection ‑> { 
if (1 == 2) { 
throw new IOException(“test”); 

if (1 == 1) { 
throw new SQLException(“test”); 

});

1 package com.bluecatcode.common.concurrent;
2
3 import org.junit.Rule;
4 import org.junit.Test;
5 import org.junit.rules.ExpectedException;
6
7 import javax.sql.DataSource;
8 import java.io.IOException;
9 import java.sql.Connection;
10 import java.sql.SQLException;
11
12 import static com.bluecatcode.common.concurrent.TryTest.TransactionalDataSource.using
13 import static org.mockito.Mockito.mock;
14
15 public class TryTest {
16
17     @Rule
18     public ExpectedException exception = ExpectedException.none();
19
20     @Test
21     public void shouldNotThrow() throws IOException, SQLException {
22         DataSource dataSource = mock(DataSource.class);
23
24         using(dataSource).with(
25                 connection ‑> {
26                     if (1 == 2) {
27                         throw new IOException(ʺtestʺ);
28                     }
29                     if (1 == 3) {
30                         throw new SQLException(ʺtestʺ);
31                     }
32                 });
33     }
34
35     @Test
36     public void shouldThrowIOException() throws IOException, SQLException {
37         DataSource dataSource = mock(DataSource.class);
38
39         exception.expect(IOException.class);
40
41         using(dataSource).with(
42                 connection ‑> {
43                     if (1 == 1) {
44                         throw new IOException(ʺtestʺ);
45                     }
46                     if (1 == 2) {
47                         throw new SQLException(ʺtestʺ);
48                     }
49                 });
50     }
51
52     @Test
53     public void shouldThrowSQLException() throws IOException, SQLException {
54         DataSource dataSource = mock(DataSource.class);
55
56         exception.expect(SQLException.class);
57
58         using(dataSource).with(
59                 connection ‑> {
60                     if (1 == 2) {
61                         throw new IOException(ʺtestʺ);
62                     }
63                     if (1 == 1) {
64                         throw new SQLException(ʺtestʺ);
65                     }
66                 });
67     }
68
69     public interface TransactionalDataSource extends Transactional2<IOException, SQLException
70         DataSource dataSource();
71
72         @Override
73         default Connection connection() throws SQLException {
74             return dataSource().getConnection();
75         }
76
77         static TransactionalDataSource using(DataSource dataSource) {
78             return () ‑> dataSource;
79         }
80     }
81
82     public interface Transactional1<E1 extends Exception>
83             extends Transactional<E1, VoidException, VoidException, VoidException, VoidException
84         // Alias
85     }
86
87     public interface Transactional2<E1 extends Exception, E2 extends Exception>
88             extends Transactional<E1, E2, VoidException, VoidException, VoidException> {
89         // Alias
90     }
91
92     public interface Transactional3<E1 extends Exception, E2 extends Exception, E3 extends 
93             extends Transactional<E1, E2, E3, VoidException, VoidException> {
94         // Alias
95     }
96
97     public interface Transactional4<E1 extends Exception, E2 extends Exception, E3 extends 
98             extends Transactional<E1, E2, E3, E4, VoidException> {
99         // Alias
100     }
101
102     public interface Transactional5<E1 extends Exception, E2 extends Exception, E3 extends 
103             extends Transactional<E1, E2, E3, E4, E5> {
104         // Alias
105     }
106
107     public interface Transactional<E1 extends Exception, E2 extends Exception, E3 extends Except
108             extends WithConnection {
109
110         default void with(ThrowingConsumer<Connection, E1, E2, E3, E4, E5> logic) throws SQLEx
111             try (Connection connection = connection()) {
112                 logic.accept(connection);
113             }
114         }
115     }
116
117     public interface WithConnection {
118         Connection connection() throws SQLException;
119     }
120
121     public interface ThrowingConsumer1<T, E1 extends Exception> extends ThrowingConsumer<
122         // Alias
123     }
124
125     public interface ThrowingConsumer2<T, E1 extends Exception, E2 extends Exception> extend
126         // Alias
127     }
128
129     public interface ThrowingConsumer3<T, E1 extends Exception, E2 extends Exception, E3
130         // Alias
131     }
132
133     public interface ThrowingConsumer4<T, E1 extends Exception, E2 extends Exception, E3
134         // Alias
135     }
136
137     public interface ThrowingConsumer5<T, E1 extends Exception, E2 extends Exception, E3
138         // Alias
139     }
140
141     public interface ThrowingConsumer<T, E1 extends Exception, E2 extends Exception, E3 exten
142
143         /**
144          * Performs this operation on the given argument.
145          *
146          * @param input the input to consume
147          * @throws E1 if unable to compute
148          * @throws E2 if unable to compute
149          * @throws E3 if unable to compute
150          * @throws E4 if unable to compute
151          * @throws E5 if unable to compute
152          */
153         void accept(T input) throws E1, E2, E3, E4, E5;
154     }
155
156     public static class VoidException extends RuntimeException {
157         private VoidException() {
158             throw new UnsupportedOperationException();
159         }
160     }
161 }

view raw TransactionalDataSource.java hosted with   by GitHub

Reply
javadevguy says:
March 4, 2016 at 7:31 am
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.

Reply
Paweł Prażak says:
March 4, 2016 at 10:17 am
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.

Eleftheria Drosopoulou says:
June 23, 2016 at 1:19 pm
Hello Robert,

Nice blog! I am editor at Java Code Geeks (www.javacodegeeks.com). We have the JCG program
(see hퟙ�p://www.javacodegeeks.com/join‑us/jcg/), that I think you’d be perfect for.

If you’re interested, send me an email to eleftheria.drosopoulou.javacodegeeks.com and we can
discuss further.

Best regards, 
Eleftheria Drosopoulou

Reply
Create a free website or blog at WordPress.com.

S-ar putea să vă placă și