Java 8: Mandatory checked exceptions handling in lambda expressions. Why mandatory, not optional?

46,864

Solution 1

Not sure I really answer your question, but couldn't you simply use something like that?

public final class SupplierUtils {
    private SupplierUtils() {
    }

    public static <T> Supplier<T> wrap(Callable<T> callable) {
        return () -> {
            try {
                return callable.call();
            }
            catch (RuntimeException e) {
                throw e;
            }
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
}

public class JdbcConnectionPool extends ObjectPool<Connection> {

    public JdbcConnectionPool(int maxConnections, String url) {
        super(SupplierUtils.wrap(() -> DriverManager.getConnection(url)), maxConnections);
    }
}

Solution 2

In the lambda mailing list this was throughly discussed. As you can see Brian Goetz suggested there that the alternative is to write your own combinator:

Or you could write your own trivial combinator:

static<T> Supplier<T> exceptionWrappingSupplier(Supplier<T> b) {
     return e -> {
         try { b.accept(e); }
         catch (Exception e) { throw new RuntimeException(e); }
     };
}

You can write it once, in less that the time it took to write your original e-mail. And similarly once for each kind of SAM you use.

I'd rather we look at this as "glass 99% full" rather than the alternative. Not all problems require new language features as solutions. (Not to mention that new language features always causes new problems.)

In those days the Consumer interface was called Block.

I think this corresponds with JB Nizet's answer.

Later Brian explains why this was designed this way (the reason of problem)

Yes, you'd have to provide your own exceptional SAMs. But then lambda conversion would work fine with them.

The EG discussed additional language and library support for this problem, and in the end felt that this was a bad cost/benefit tradeoff.

Library-based solutions cause a 2x explosion in SAM types (exceptional vs not), which interact badly with existing combinatorial explosions for primitive specialization.

The available language-based solutions were losers from a complexity/value tradeoff. Though there are some alternative solutions we are going to continue to explore -- though clearly not for 8 and probably not for 9 either.

In the meantime, you have the tools to do what you want. I get that you prefer we provide that last mile for you (and, secondarily, your request is really a thinly-veiled request for "why don't you just give up on checked exceptions already"), but I think the current state lets you get your job done.

Solution 3

We developed an internal project in my company that helped us with this. We decided to went public two months ago.

This is what we came up with:

@FunctionalInterface
public interface ThrowingFunction<T,R,E extends Throwable> {
R apply(T arg) throws E;

/**
 * @param <T> type
 * @param <E> checked exception
 * @return a function that accepts one argument and returns it as a value.
 */
static <T, E extends Exception> ThrowingFunction<T, T, E> identity() {
    return t -> t;
}

/**
 * @return a Function that returns the result of the given function as an Optional instance.
 * In case of a failure, empty Optional is returned
 */
static <T, R, E extends Exception> Function<T, Optional<R>> lifted(ThrowingFunction<T, R, E> f) {
    Objects.requireNonNull(f);

    return f.lift();
}

static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
    Objects.requireNonNull(f);

    return f.uncheck();
}

default <V> ThrowingFunction<V, R, E> compose(final ThrowingFunction<? super V, ? extends T, E> before) {
    Objects.requireNonNull(before);

    return (V v) -> apply(before.apply(v));
}

default <V> ThrowingFunction<T, V, E> andThen(final ThrowingFunction<? super R, ? extends V, E> after) {
    Objects.requireNonNull(after);

    return (T t) -> after.apply(apply(t));
}

/**
 * @return a Function that returns the result as an Optional instance. In case of a failure, empty Optional is
 * returned
 */
default Function<T, Optional<R>> lift() {
    return t -> {
        try {
            return Optional.of(apply(t));
        } catch (Throwable e) {
            return Optional.empty();
        }
    };
}

/**
 * @return a new Function instance which wraps thrown checked exception instance into a RuntimeException
 */
default Function<T, R> uncheck() {
    return t -> {
        try {
            return apply(t);
        } catch (final Throwable e) {
            throw new WrappedException(e);
        }
    };
}

}

https://github.com/TouK/ThrowingFunction/

Solution 4

September 2015:

You can use ET for this. ET is a small Java 8 library for exception conversion/translation.

With ET you can write:

super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections);

Multi line version:

super(() -> {
  return et.withReturningTranslation(() -> DriverManager.getConnection(url));
}, maxConnections);

All you need to do before, is creating a new ExceptionTranslator instance:

ExceptionTranslator et = ET.newConfiguration().done();

This instance is thread safe an can be shared by multiple components. You can configure more specific exception conversion rules (e.g. FooCheckedException -> BarRuntimeException) if you like. If no other rules are available, checked exceptions are automatically converted to RuntimeException.

(Disclaimer: I am the author of ET)

Solution 5

Have you considered using a RuntimeException (unchecked) wrapper class to smuggle the original exception out of the lambda expression, then casting the wrapped exception back to it's original checked exception?

class WrappedSqlException extends RuntimeException {
    static final long serialVersionUID = 20130808044800000L;
    public WrappedSqlException(SQLException cause) { super(cause); }
    public SQLException getSqlException() { return (SQLException) getCause(); }
}

public ConnectionPool(int maxConnections, String url) throws SQLException {
    try {
        super(() -> {
            try {
                return DriverManager.getConnection(url);
            } catch ( SQLException ex ) {
                throw new WrappedSqlException(ex);
            }
        }, maxConnections);
    } catch (WrappedSqlException wse) {
        throw wse.getSqlException();
    }
}

Creating your own unique class should prevent any likelihood of mistaking another unchecked exception for the one you wrapped inside your lambda, even if the exception is serialized somewhere in the pipeline before you catch and re-throw it.

Hmm... The only thing I see that's a problem here is that you are doing this inside a constructor with a call to super() which, by law, must be the first statement in your constructor. Does try count as a previous statement? I have this working (without the constructor) in my own code.

Share:
46,864
Lyubomyr Shaydariv
Author by

Lyubomyr Shaydariv

... and I fail at interviews.

Updated on July 05, 2022

Comments

  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv almost 2 years

    I'm playing with the new lambda features in Java 8, and found that the practices offered by Java 8 are really useful. However, I'm wondering is there a good way to make a work-around for the following scenario. Suppose you have an object pool wrapper that requires some kind of a factory to fill the object pool, for example (using java.lang.functions.Factory):

    public class JdbcConnectionPool extends ObjectPool<Connection> {
    
        public ConnectionPool(int maxConnections, String url) {
            super(new Factory<Connection>() {
                @Override
                public Connection make() {
                    try {
                        return DriverManager.getConnection(url);
                    } catch ( SQLException ex ) {
                        throw new RuntimeException(ex);
                    }
                }
            }, maxConnections);
        }
    
    }
    

    After transforming the functional interface into lambda expression, the code above becomes like that:

    public class JdbcConnectionPool extends ObjectPool<Connection> {
    
        public ConnectionPool(int maxConnections, String url) {
            super(() -> {
                try {
                    return DriverManager.getConnection(url);
                } catch ( SQLException ex ) {
                    throw new RuntimeException(ex);
                }
            }, maxConnections);
        }
    
    }
    

    Not so bad indeed, but the checked exception java.sql.SQLException requires a try/catch block inside the lambda. At my company we use two interfaces for long time:

    • IOut<T> that is an equivalent to java.lang.functions.Factory;
    • and a special interface for the cases that usually require checked exceptions propagation: interface IUnsafeOut<T, E extends Throwable> { T out() throws E; }.

    Both IOut<T> and IUnsafeOut<T> are supposed to be removed during migration to Java 8, however there is no exact match for IUnsafeOut<T, E>. If the lambda expressions could deal with checked exceptions like they were unchecked, it could be possible to use simply like the following in the constructor above:

    super(() -> DriverManager.getConnection(url), maxConnections);
    

    That looks much cleaner. I see that I can rewrite the ObjectPool super class to accept our IUnsafeOut<T>, but as far as I know, Java 8 is not finished yet, so could be there some changes like:

    • implementing something similar to IUnsafeOut<T, E>? (to be honest, I consider that dirty - the subject must choose what to accept: either Factory or "unsafe factory" that cannot have compatible method signatures)
    • simply ignoring checked exceptions in lambdas, so no need in IUnsafeOut<T, E> surrogates? (why not? e.g. another important change: OpenJDK, that I use, javac now does not require variables and parameters to be declared as final to be captured in an anonymous class [functional interface] or lambda expression)

    So the question is generally is: is there a way to bypass checked exceptions in lambdas or is it planned in the future until Java 8 is finally released?


    Update 1

    Hm-m-m, as far as I understand what we currently have, it seems there is no way at the moment, despite the referenced article is dated from 2010: Brian Goetz explains exception transparency in Java. If nothing changed much in Java 8, this could be considered an answer. Also Brian says that interface ExceptionalCallable<V, E extends Exception> (what I mentioned as IUnsafeOut<T, E extends Throwable> out of our code legacy) is pretty much useless, and I agree with him.

    Do I still miss something else?

  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 11 years
    Thanks! Yes, your approach seems to be a possible solution in the terms of current Java 8 features (this also was suggested by Matt in a post comment via Guava Throwables java8blog.com/post/37385501926/… ). It also seems to be the most elegant solution, but, I guess you also agree, that's a little boiler-plate. Brian Goetz in 2010 tried something to solve this problem using something like variadic type parameters, and probably Java will get this fixed somehow, who knows.
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 11 years
    The wrapping is described just like it could imitate a checked-exception-less Supplier<T> instead of Callable<T> -- that is a surrogate way and it works for most cases that I was faced with. I personally would change the behavior of lambda expressions / functional interfaces for checked exceptions. But I have doubt that the lambda-dev team will agree to change that behavior for lambda's. Unfortunately Java checked exceptions are good by intention, but harmful in practice. This is similar to wrapping a checked exception into RuntimeException within try/catch in every method.
  • Waldo auf der Springe
    Waldo auf der Springe over 11 years
    Well, there is a way to kind of genericly wrap the checked exception which is slightly more eloborate then described, see my upcoming answer. But still, I agree with you that our programming lives would be better if the compiler would work a bit harder and allow us to throw checked exceptions in our lambdas. As it stands we are deprived of catching specific checked exceptions that occur there, forcing us into ugly cumbersome coding. yuck!
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 11 years
    By the way, you can try to suggest your idea for simplified checked exceptions handling to lambda-dev team via their mailing list. If they approve your email (not idea itself) -- you might know their pros and cons for changing the spec, and probably personal ideas and thoughts by Brian Goetz too.
  • Waldo auf der Springe
    Waldo auf der Springe over 11 years
    Thank you for the tip. I tried that actually, but the suggestion has completely been ignored. The reactions of contributions made regarding checked exceptions don't look too positive. Nevertheless I hope that people that share these concerns speak out, while there is still some time to repair it. The java lambda looks so good that this deserves fixing.
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 10 years
    Thank you for reply. I would really like to prevent explicit try/catching in lambda expressions because try/catch blocks look ugly making the lambdas ugly too. I guess there are at least two reasons of why it cannot work like that so far: 1) the history of Java itself; 2) a method, that's invoked with a lambda expression having methods with throws invocations inside, probably should be "automatically" declared as "throws SomeEx", even if the method is declared without any throws or it declares to throw exception of another type rather than could be propagated from the lambda.
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 10 years
    3) If it could "swallow" checked exceptions, I think, then it might break your expectations for a checked exception or unchecked exception you might expect to catch in any caller outside. For example, you could expect to catch IOException for currently illegal code like list.forEach(p -> outputStream.write(p.hashCode())) (write() throws IOException), but you'd probably couldn't do it, because Iterable.forEach() is not declared as a method throwing IOException, so a compiler cannot know of these intentions. Sometimes checked exceptions are really harmful...
  • GlenPeterson
    GlenPeterson over 10 years
    I agree. It would be overkill to provide a second set of java.util.function classes that declare that they throw a (checked) Exception. Especially since we'd need two versions of every method with a function parameter - one that throws and one that doesn't. That goes a long way toward explaining why Scala doesn't have checked exceptions (only unchecked). Java 8 is a copy of Scala's lambdas with a -> instead of =>. I wonder if Martin Odersky suggested any of this while he was working at Sun? I hope Java 9 includes immutable versions of most of their API classes.
  • Gili
    Gili about 10 years
    Yet another great example of checked exception abuse. Wrapping the exception in RuntimeException might be necessary in this case, but failing to unwrap it again on the outside defeats the entire point of checked exceptions. Please take the time to read why checked exceptions exist and when they should be used: stackoverflow.com/a/19061110/14731
  • Marko Topolnik
    Marko Topolnik about 10 years
    @Gili Checked exceptions are an abuse of programmer, not vice versa. They promote boilerplate code, incidental complexity, and downright programming errors. They break program flow, especially because try-catch is a statement and not an expression. Wrapping exceptions is a) a nuisance, b) little more than a hack and work around the basic misdesign.
  • Marko Topolnik
    Marko Topolnik about 10 years
    99% of real-life program logic requires checked and unchecked exceptions alike to break the current unit of work and have them uniformly handled at the top-level exception barrier. Those rare cases where the exception is a part of regular program logic are not something that the programmer will tend to miss, and would need the compiler to tell him.
  • Gili
    Gili about 10 years
    @MarkoTopolnik, I respectfully disagree. Coming from a C world, I can tell you that most programmers ignore error codes returned by functions, even if they're part of the "regular program logic". I get the fact that you don't like any exceptions, be they checked or unchecked, but that doesn't mean the rest of all have to follow suit. As for handling all exceptions at a "top-level exception barrier", it sounds to me like you're just catching unrecoverable exceptions. You cannot do anything meaningful at a high-level with most recoverable exceptions.
  • Gili
    Gili about 10 years
    @MarkoTopolnik, for example: if a user tries opening a non-existent file it would make no sense to handle it at some "top level exception-barrrier". You'd need to handle it at the deepest level and prompt the user for a new path.
  • Marko Topolnik
    Marko Topolnik about 10 years
    @Gili Your example is precisely that rare case which I mention. On the server side such cases are even less frequent. As for your comparison with C, exceptions -- unlike error codes -- can only be ignored explicitly, with empty catch-blocks. Funnily enough, that's exactly what happens when devs are forced to do something about checked exceptions. Your statement that I don't like exceptions sounds like you're trying to be funny, but I'm sorry for missing the humor.
  • Marko Topolnik
    Marko Topolnik about 10 years
    @Gili At the exception barrier we handle exceptions which break the unit of work---and most do. If any step that needs to be taken fails, the unit of work has failed as well.
  • Gili
    Gili about 10 years
    @MarkoTopolnik, what is rare for you is not rare for others. It could very well be that the exceptions you deal with are unrecoverable or unexpected. That's fine. But there are a whole host of cases where this isn't the case. If you only want to use unchecked exceptions, that is your right. I would point out that server-side applications are much less likely to be interactive than client-side applications, and as such exceptions are much less likely to be recoverable. That said, try developing client-side applications for a couple of years and your experience will be exactly the opposite.
  • Marko Topolnik
    Marko Topolnik about 10 years
    @gili I have extensive experience developing at both sides and, although there is a slight difference, the ratio of all exceptions vs. those that need to be caught early is still very large. For example, user input is validated before acting upon it, and that includes such checks as whether a file exists using file.exists(), which makes a subsequent FileNotFoundException a strange and unexpected occurrence.
  • Gili
    Gili about 10 years
    @MarkoTopolnik, allow me to introduce you to the possibility of race conditions and I/O errors when reading a file. It doesn't matter how rare an exception is. It matters that it can and does happen in the wild. Having your program crash because of the WIFI connection went down in mid-read is hardly a formula for writing reliable software. Again, you are free to avoid handling such errors if you wish, but implying that everyone should do the same is a different story.
  • Marko Topolnik
    Marko Topolnik about 10 years
    @Gili Now you have invented your own claim and assigned it to me. Where do I claim the application should crash in the face of an exception? I said the current unit of work will be aborted. Now you tell me, how should the current unit of work continue when the WiFi connection is lost? If you have some heuristics in mind that will keep retrying until the connection is reestablished and so on, then that is indeed an example of a special case where exceptions are handled within the unit of work. For most purposes such heuristics are not worth it and simply aborting is good enough.
  • Gili
    Gili about 10 years
    @MarkoTopolnik, I withdraw my comment about the software crashing in the face of the WIFI connection going down. We could have a very interesting debate on this topic, but Stackoverflow's comment system doesn't work well for this kind of discussion (comments are too short, and too asynchronous). Let's agree to disagree for now ;)
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv about 10 years
    Thanks for the reply. Frankly speaking, I hoped that lambda expressions will be checked-exceptions-friendly, and the universe of checked exceptions will pass by lambdas since they are purely a compiler-driven feature. But no luck.
  • Edwin Dalorzo
    Edwin Dalorzo about 10 years
    @LyubomyrShaydariv I think the expert group struggled with several design issues. The need, requirement or constraint to keep backwards compatibility made things difficult, then we have other important issues like the lack of value types, type erasure and checked exceptions. If Java had the first and lacked of the other two the design of JDK 8 would have been very different. So, we all must understand that it was a difficult problem with lots of tradeoffs and the EG had to draw a line somewhere and make a decision. That may not always pleases us, but surely there will be workarounds suggested.
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv about 10 years
    Yes, but lacking both value types and true generics is caused with JVM legacy, despite the checked exceptions do not exist in runtime, and they are purely compiler-driven. Kotlin or Xtend do not have checked exceptions, so they do not force to catch an IOException, for example. For me, it's still not yet clear why the design team doesn't allow to omit the checked exceptions in lambda expressions. It would be nice to have an example scenario where omitting a checked exception in a lambda expression might break the expectations.
  • Edwin Dalorzo
    Edwin Dalorzo about 10 years
    @LyubomyrShaydariv A functional interface is just like any other interface. You can implement it through a lambda expression or manually. If you would throw a checked exception within the implementation of an interface method that does not declare to throw that exception you are breaking the semantics of the programming language. You are throwing an exception not expected by the code dealing with implementations of the interface. I do not see that is OK. I will need a more elaborate example of your proposition for it to make sense to me. Maybe you should create another discussion for that.
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv about 10 years
    Well, so the only thing I currently see, if lambda expressions were checked-exceptions-friendly, is: try { iDontHaveThrowsDeclared(x -> someIOOperation(x)) } catch ( IOException ) { ... } won't compile, because IOException in the catch block is known to be a checked one and the iDontHaveThrowsDeclareddoesn't have a throws clause. So yes, it breaks.
  • YoYo
    YoYo over 9 years
    @EdwinDalorzo, to the user a Lambda Expression is like it does not have a function body, it is like it runs completely within the context of where the lambda expression is written. For the same reason, it seems natural to expect that the exception can be handled in that same context, outside the lambda expression, within the method. The problem is that the actual execution can happen at a much - much later point, in a totally different context, maybe as part of an API. We cannot expect an API to handle every conceivable checked Exception. Wrapping in an unchecked exception seems appropriate.
  • Hakanai
    Hakanai over 9 years
    @Gili surely this specific example is not recoverable anyway. In pretty much any system I have seen, if DriverManager.getConnection fails, something is seriously wrong. If you're in a webapp, the odds are the configuration is incorrect. If you're in a GUI app, the odds are you're using an embedded database and hard-coding the path. So I would never expect getConnection to throw - yet the API forces us to catch it for no good reason. Checked exceptions may have had a theoretical use case, but their inconsistent application in the JRE has made them worse than useless.
  • Gili
    Gili over 9 years
    @Trejkaz, you're using a strawman argument. If you read stackoverflow.com/questions/27578/… you'll see I highlight SQLException as one of the most abused exceptions around. Checked exceptions have a place, but SQLException is not it.
  • Hakanai
    Hakanai over 9 years
    @Gili A "strawman argument" is an argument where I construct one example of a case where my viewpoint is correct and then use it to imply that my viewpoint is correct. The code fragment above uses SQLException, so I believe that in this situation, I am not using a strawman argument as I did not provide the example. Note that I did not say that checked exceptions are useless. I specifically said that checked exceptions in Java are worse than useless. Which is still correct.
  • Gili
    Gili over 9 years
    @Trejkaz, not all exceptions are SQLException yet you seem to be extrapolate from the abusive usage of SQLException to saying all checked exceptions in Java are "worse than useless". I am saying that the first point does not imply the second point.
  • Hakanai
    Hakanai over 9 years
    @Gili Okay, let's flip this around. Can you even name one checked exception in the JRE which is applied consistently and correctly throughout the JRE? I would be interested in hearing your counter-example, because I have not actually discovered a single one yet. (And anyway, I didn't extrapolate from abusive usage of one exception. I extrapolated from a 15 year experience of running into all the other cases where they couldn't seem to get it right.)
  • Gili
    Gili over 9 years
    @Trejkaz java.io.FileNotFoundException comes to mind. As do any I/O operations where it's reasonable to retry on failure (or ask the user for an alternative file). SQLException typically maps to programming errors, but IOException typically maps to runtime errors outside the programmer's control (and in many cases, these failures are recoverable).
  • Hakanai
    Hakanai over 9 years
    I disagree with that case. When the user enters a file path, it is expected they they can enter an invalid path. Since there is a general rule not use exceptions for normal flow control, in this situation I would check that the file exists before opening it. Therefore, having a FileNotFoundException on constructors of stream classes is just a nuisance to me - a second place I will have to catch.
  • JB Nizet
    JB Nizet over 9 years
    @Trejkaz the problem is that you can't have any guarantee that, after you've checked the file existed and you decide to open it, it still exists. Yo need to take race conditions into account.
  • Hakanai
    Hakanai over 9 years
    @JBNizet Sure, but I would argue that sort of condition to be relatively rare and thus not something I should have to take care of with boilerplate. :(
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 8 years
    That's cool stuff, and it's nice to be a part of your inspiration. :) Three years after the post I can't completely refuse the exceptional twins of the standard functional interfaces. I'm pretty sure it's just because I'm too lazy to create yet another business logic interface. I usually never combine them with streams processing, and I think that checked exceptions can be a great option especially for cross-layer communucation.
  • Lyubomyr Shaydariv
    Lyubomyr Shaydariv over 8 years
    I think now, over those years, that my initial question was wrong from the design perspective: why did I want a Supplier instance to be a factory at all? Now I think that that dirty constructor in the post should just delegate something like a IConnectionFactory that declares an SQLException to be thrown just to clearly reveal the intentions of such an interface that might be more or less easily extended in the future.
  • maaartinus
    maaartinus almost 7 years
    @JBNizet After having checked that "foo.xml" exists, many things can go wrong. The file disappearing is what I'd fear least, there may be problems with missing read permissions, failing disk, invalid content or bugs in the parser... and none of the problems can be really solved. All of them must be handled somehow, and I can't see any outstanding solution for FileNotFoundException (offering the user to open "bar.json" instead doesn't count :D). All of the problems may or may not be still present when you retry.
  • XZen
    XZen over 6 years
    Do you know if this already implemented in Spring / apache commons etc? I realize it's a small piece of code but still :D
  • Bill K
    Bill K over 6 years
    It is strange that @JBNizet comes up with a clearly case that proves the point against checked exceptions. If forced to catch an exception after checking for the files existence, many programmers would consume the exception without logging (They DO, I've seen it) when they would have ignored it allowing it to be logged if it had been unchecked. Consuming a single checked exception without handling it can cost more developer time than almost any other problem... I've actually spent a full week tracking one of those that would have taken minutes if it had been unchecked and therefore logged.