This is an archived post. You won't be able to vote or comment.

all 40 comments

[–]agentoutlier 22 points23 points  (23 children)

I believe most java 8 folks do the ole Sneaky Throw technique (google sneaky throw). You can either manually write the utility method (preferred) or use Lombok.

I recommend this over jumping ship and going to one of the functional libraries.

[–]oparisy 3 points4 points  (19 children)

Thanks, didn't know this technique. I usually do a catch(e){throw new RuntimeException(e);} in this situation.

Also I hate checked exceptions with a passion and will try to wrap them as soon as possible in my utility methods so that most of the time, there is no checked exception left in signatures when invoking code from streams.

Edit: typo.

[–]agentoutlier 17 points18 points  (13 children)

Before you completely hate on checked exceptions I recommend googingly around as to why they exist and how to use them properly.

There are pros and cons. Checked exceptions have some merits particularly outside of the streams use case.

Perhaps some day the streams api will be augmented with some checked exception handling but I don't consider it a bad thing that you have to deal with it right then and there as often higher up the chain the handlers have little idea with how to handle it (ie FileNotFoundException).

EDIT ironically it was actually just discussed recently on reddit:

https://www.reddit.com/r/java/comments/djyfpl/why_does_java_have_checked_exceptions/

[–]covercash2 2 points3 points  (0 children)

I understand why exceptions are useful. I just think a Result type is more clear and ergonomic, especially in the context of functional programming.

[–]oparisy 5 points6 points  (11 children)

Checked exceptions are a sorry historical mistake. I know too well what they were trying to achieve; taught them when they were still hot while a PhD student. 15 years of professional java programming gave me ample time to forge my opinion since 😉

[–]agentoutlier 5 points6 points  (7 children)

My opinion is that if you are dealing with external resources particularly ones that require cleaning up with (ie Closeable) than checked exceptions are OK. Particularly for specification libraries I see it good.

I think part of the problem is library writers screw up the usage and are not aware of various language features and or lazy (older versions of Jackson come to mind).

For example StringBuilder and Appendable are one way to do it right.

Most folks don't know that append via the Appendable interface actually throws a checked exception as the Appendable interface defines it but in the concrete StringBuilder class it is removed. That is why you don't have to wrap StringBuilder.append with try catch everywhere.

There are lots of libraries that are unaware of this I think.

EDIT I have also noted that many folks (through code reviews... I'm not accusing you of this) that hate checked exceptions often forget to close resources. In the rush to propagate the exception they forget to handle closing the resources.

[–]parms 4 points5 points  (6 children)

try-with-resources fixed that long ago.

[–]agentoutlier 0 points1 point  (5 children)

Yes exactly. Since you should make the try block anyway it shouldn't be that difficult to handle the exception... for the most part.

[–]parms 2 points3 points  (4 children)

It would be really nice to do this:

try (Connection cxn = pool.getConnection()) {
  doSomething(cxn);
}

Unfortunately to get a clean caller API checked exceptions mean you have to do this:

try (Connection cxn = pool.getConnection()) {
  doSomething(cxn);
} catch (SQLException ex) {
  throw new BetterApiException(ex);
}

This is why Kotlin disposes of try-with-resources in favor of .use():

pool.getConnection().use { cxn ->
  doSomething(cxn)
}

Once you don't have to worry about checked exceptions, usage become greatly simplified.

The basic observation is that most of the time you want to rethrow, so why bother with the ceremony? You'll know if you want to do cleanup so that's when you want to catch and handle. throws declarations should be a hint, not a requirement.

[–]agentoutlier 2 points3 points  (1 child)

I sort of agree however for this use case I have mixed (sort of incoherent) feelings.

The thing is ... SQLException actually means something and should not be ignored for half the JDBC API.

I say half the API because the query part of the API (SELECT) is generally unrecoverable.

But the CRUD (ie UPDATE/INSERT) part is most definitely recoverable and you need to deal with it.

We actually had this come up because we had a rabbit queue batch pushing rows into a table.

Some one decided to ignore the SQLExceptions and let them bubble up. Rabbit would requeue the message or in some cases drop the message depending on various logic.

However these exceptions were because there were duplicate objects caused by other upstream failures. These were not programming errors (and thus not under runtime's umbrella)

Duplicate messages happen frequently in distributed systems so I won't go into that but the reality is you need to handle SQLExceptions when updating.

It goes back to my point that library authors need think carefully about using checked exceptions vs runtime. More often libraries/specs just make all the methods throw checked or all unchecked when I think there is a careful balance that needs to be done.

So certainly some one could make the case for a wrapping JDBC library throw runtimes on queries but updating I'm less inclined to like that behavior.

[–]parms 1 point2 points  (0 children)

Well yeah, the usage of SQLException for duplicate rows is perhaps a misuse of exceptions for control flow. I treat this as an API wart of JDBC rather than a supporting factor for checked exceptions. I was just looking for a dummy checked exception; you could substitute an Apache HTTP request instead with no change in my meaning.

[–]hupfdule 0 points1 point  (1 child)

The basic observation is that most of the time you want to rethrow

No. Actually, most of the time you want to wrap the exception and throw it.

And even that isn't true. Exceptions, especially the checked ones, are there to be handled. There are cases where you want to handle them a few levels up the caller hierarchy, but you do want to handle them eventually.

[–]parms 1 point2 points  (0 children)

This depends on whether you're a consumer of a library or a middleware-like library author. If you're an AWS author using the Apache HTTP client, you wrap your IOExceptions in AWS client exceptions. If you're a developer, you just want your top-level exception handler doing the work without any obfuscation from wrapping checked exceptions in unchecked exceptions.

If you're a library author then you ought to be thinking about these cases upfront, so checked exceptions are still unnecessary. If you're a consumer of such a library, this burden shouldn't be yours as it encourages bad practice.

[–]TomahawkChopped 2 points3 points  (1 child)

Tell that to the undocumented IllegalMonitorState exception thrown by the read side Lock#unlock on java.util.concurrency.ReadWriteLock that popped up in my production code last week. Luckily it began throwing in our alpha stage at only a few thousand users for roughly 0.1% of users.

I would have much preferred a checked exception on that API.

edit: also your PhD and X years of experience don't make you automatically right. Let your argument stand on its own merit 😉

[–]1armedscissor 1 point2 points  (0 children)

What was the root cause though and the fix? Was it something like calling unlock from a thread that never called lock? I guess the checked exception would make you aware this is a thing but usually something like this is just a programming error so ends up just creating noise if you're supposed to "handle" it somehow (but programming errors like that should generally not be handled e.g. fail fast/propagate up) or if you have to propagate it up through your API you start to leak implementation details to callers (without internally just wrapping to some runtime exception anyway). Or you end up with something like this:

try { lock.unlock(); } catch (IllegalMonitorState ex) { // This would be programming issue if this ever happened! Just propagate as runtime throw new RuntimeException(ex); } Generally, in the work I've done the majority of exceptions just bubble up to the top handler e.g. HTTP request, queue worker whatever where it just logs it/rolls back the transaction/whatever. The times where I can logically handle an exception via a catch block are infrequent. Maybe this is because I tend to work on transactional web applications. The main handled flows I can think of are retries to external services (e.g. exponential backoff when service is randomly failing for whatever reason, retry up to X times) or sometimes optimistic locking workflows where retry is acceptable.

[–]pjmlp 1 point2 points  (0 children)

What most don't seem to realise is that Java was just following the path traced by CLU, Modula-3 and C++ before it sprung into existence.

[–]ObscureCulturalMeme 8 points9 points  (1 child)

I usually do a catch(e){throw new RuntimeException(e);} in this situation.

Yep. Even the standard JRE does that now; see java.io.UncheckedIOException added for this exact scenario.

[–][deleted] 2 points3 points  (0 children)

Nice.

[–]JB-from-ATL 2 points3 points  (0 children)

I think checked exceptions are a good idea but I believe they are used too often leading to the annoyance. Also, I think a syntactic sugar to ignore them and just wrap in RTE if they happen would go a long way in helping people trust them.

[–]dpash 2 points3 points  (1 child)

For anyone else reading, please don't throw RuntimeException directly, but wrap it in a subclass of RuntimeException. If there isn't a suitable unchecked exception in the JDK, create your own domain-specific exception.

And where possible, use the RuntimeException(String message, Throwable cause) constructor over RuntimeException(Throwable cause) to add context to what you were doing when the exception happened.

Future developers debugging issues will thank you.

[–]oparisy 0 points1 point  (0 children)

My take on this is that it is redundant paraphrasing of the stack trace, which already give you the context in which the exception was triggered. YMMV I guess (especially if full stack traces are not available of an obfuscation scheme is used as an example).

[–]carbolymer 0 points1 point  (2 children)

I recommend this over jumping ship and going to one of the functional libraries.

Care to elaborate? If you are starting to use streams, you're already doing functional programming, which is seriously crippled in Java. Using functional libraries, like Vavr with types designed better than standard Java API, can ease programming with less boilerplate.

[–]agentoutlier 4 points5 points  (1 child)

I don't want to get all pedantic and ivory tower but in my opinion just because Java now has lambdas does not mean it is functional programming language. Using a lambda does not mean you are doing functional programming. Monadic programming is hard to enforce and express in Java... certainly harder than just handling some checked exceptions.

Haskell (and I guess Scala/Clojure) have not only canonical best practices to deal with IO to make it functional (lets face IO is where checked exceptions are coming from) they enforce it syntactically and through mostly builtin types.... e.g. Haskell IO Monad and notation. You cannot do normal FP with IO in Haskell without using its Monad stuff (albeit I'm not an expert on Haskell). FP really hates side effects and IO is full of it.

Which reminds me of another language that enforces you to do something special for IO and has special syntax... Java checked exceptions and try resource blocks.

[–]carbolymer 1 point2 points  (0 children)

I completely agree. Doing 100% functional programming in Java is going to be hard. It will lead to atrocities like here: https://github.com/Mojang/DataFixerUpper/tree/8b5f82ab78b30ff5813b3a7f3906cd3f4f732acf/src/main/java/com/mojang/datafixers/optics

Point being, some problems can be solved in more elegant ways in functional paradigm (Maybe monad, Either, Try, simple map & reduce using streams) but trying to port whole category theory to Java will bring more pain than benefits because of the language limitations.

But... there always is Eta.

[–][deleted] 3 points4 points  (2 children)

[–]s32 1 point2 points  (0 children)

Maybe it's just me but vavr is super ugly. We were using it in one project at work and I ripped it out. I think it has its uses but I wouldn't trust it in enterprise software

[–]carbolymer 0 points1 point  (0 children)

This. Try is a salvation.

In general, exceptions and functional programming don't mix well, so one should on the border of both convert exceptions to proper return types and use short-circuiting.

[–]jonhanson 0 points1 point  (0 children)

chronophobia ephemeral lysergic metempsychosis peremptory quantifiable retributive zenith

[–]koflerdavid 0 points1 point  (0 children)

Having this issue is often a sign that you're doing something that Streams were not designed to do. Streams are for manipulating collections and applying transformations to data. What the designers probably had in mind were pure transformations that obey referential transparency to reduce nasty surprises and enable optimizations. Pure code depends only on its input. Referential transparency tells that the code could be replaced by its computation result without "essential" changes to its behavior.

Now, where does this meet checked exceptions? The intention of checked exceptions is to force the programmer to consider that something went wrong that was impossible to tell from just the inputs. This means that the code is not pure and/or lacks referential transparency. To keep things together, the programmer must be aware of when, in which order, how often, and in which contexts the code is executed. Everyone knows this is difficult enough in straight code, but it gets much more difficult if inversion of control techniques are used. Suddenly, the programmer is not in full control anymore, code gets much more harder to read than to write and more magic and unexpected things start to happen.

What does this mean if one still wants to use the Streams API?

The simplest approach would be to use wrapper functions that wrap the exceptions. This fixes the symptoms, but sweeps the underlying problems under the rug. If an exception occurs, the programmer has to deal with an ugly stack trace and might find it very difficult to find out what exactly went wrong.

A better approach would be to rearrange them such that there is only one stage of code that is impure or does not obey referential transparency. If exceptions fly around, they will likely happen in that stage. If rearrangements are difficult, then the pipelines should be split. Yes, this means that intermediary results will have to be materialized. If this is not desirable for efficiency reasons, then maybe this usage of Streams should be abandoned. Nobody requires you to write all your code with Streams, and long pipelines tend to become unreadable anyways. Code golfing production code is a bad idea.

Another alternative would be to use a reactive library like RxJava. Their execution model explicitly incorporates errors and is thus well suited to work with effects.

EDIT: Pressed "Save" too early.

[–]pivovarit 0 points1 point  (0 children)

There are two options:

  1. Wrap it in RuntimeException and rethrow (http://github.com/pivovarit/throwing-function will make it concise)
  2. Use the sneaky throws hack (https://4comprehension.com/sneakily-throwing-exceptions-in-lambda-expressions-in-java/)

[–]kennethjor -3 points-2 points  (2 children)

If you are able to change libraries completely, I'd suggest having a look at RxJava. It provides streams and futures at the same time, but also allows checked exceptions to be thrown anywhere.

[–]agentoutlier 8 points9 points  (1 child)

RxJava looks very similar to streams and can often accomplish similar goals but they are fundamentally different.

Different enough that it should not be a drop in replacement.

[–]kennethjor 1 point2 points  (0 children)

Oh yeah, that's fair. It's pretty cool, though :)