25 ms·
Checked exceptions: Java’s biggest mistake (2014)
- pjmlp 6y agoI sorely miss them in .NET, specially with libraries without any kind of documentation regarding the errors that they throw. Also checked exception haters always overlook the fact that CLU, Modula-3 and C++ did it first.
- hodgesrm 6y agoAnd badly in at least the case of C++. I was a fan of Java checked exceptions initially because they actually worked compared to C++ exceptions, which conflicted with threading models based on setmp/longjmp. We banned them in my projects in the mid-1990s for this reason. (We were programming on Sybase OpenServer before it implemented native threads.)
- zokier 6y agoIf anything, I think unchecked exceptions were the bigger mistake in Java. Checked exceptions are pretty much isomorphic to result-types that are oh so fashionable these days (see also Rust), unchecked exceptions on the other hand are completely invisible and unpredictable crazyness.
- Guvante 6y ago`OutOfMemoryError` is the root of unchecked exceptions, you can't put it everywhere because then it might as well be nowhere. Rust improves on this by using a different syntax for unchecked exceptions (`panic!` vs `Result`) which provides the benefit of discouraging unchecked without preventing it.
- tsimionescu 6y agoAren't panics() in Rust unrecoverable in release builds? Except for this minor point, I agree - you can't really make a managed memory system without resorting to unchecked exceptions. Array index out of bounds is another good example of an almost unavoidable exception that would only pollute the code if it had to be checked everywhere (as is Null pointer exception, but that of course could be mitigated by not having nulls in the language).
- ragnese 6y agoI don't believe so, no. You can set panics to automatically abort, though. But that's opt-in. The real point is that panics are absolutely unchecked exceptions, but it's really awkward to catch them and the standard library and entire Rust ecosystem has a good, solid, culture around returning Results whenever reasonable. The problem with Java is that a bunch of people have no idea what they are "supposed" to do with the unhappy path parts of their programs.
- tsimionescu 6y agoWell, as long as the option of disabling panic handling exists, and if it is somewhat widely used in real applications, library writers can't rely on panics as an error-handling mechanism, so generally they have to treat it as if panics can abort the program. I think the problem of deciding what to do on the unhappy path is often very difficult, a lot of the time much harder than the actual happy path. This is true regardless of the error reporting/bubbling mechanism.
- Guvante 6y agoThere are unwind panics and abort panics, it is the choice of the one panicking. Also if you are a library maintainer the binary can choose to disable unwind panics completely. On the topic of array index out of bounds Rust provides three ways to do indexing `obj[i]` which panics on out of bounds, `obj.get(i)` which returns `Option<T>` and `obj.get_unchecked(i)` which is the C/C++ style "if I go outside the bounds of the array it is undefined behavior" (and thus is marked unsafe). The first avoids polluting and provides the easiest syntax, the second allows you to opt into "I don't know if this is in bounds" and the final one is designed for instances where you otherwise can prove the index is in bound to avoid the conditionals to check.
- tsimionescu 6y agoC++ never had checked exceptions in Java's sense. The only purpose of declaring exceptions was so that the runtime could throw a nastier error if some exception other than the listed ones was raised. The modern noexcept specifier is closer to checked exceptions, but has learned some of Java's mistakes (for example, a function template can be conditionally noexcept based on input arguments).
- pjmlp 6y agoThe concept was the same, though. And they were the inspiration for Java's.
- sedatk 6y agoLibraries can convey that information through IntelliSense with an XML comment: <exception cref="FileNotFoundException">Thrown when file is not found</exception>
- pjmlp 6y agoHave you missed the "specially with libraries without any kind of documentation" part of the sentence?
- sedatk 6y agoXML documentation is part of the package and development experience when using an IDE. It’s way different than trying to find some random Wiki page online. I don’t consider them equivalent.
- gordaco 6y agoI don't really dislike the concept of checked exceptions, but the awful way they prevent the usage of functional interfaces and modern Java in general is pretty infuriating. Functional interfaces and code that uses them should have a generic way of being transparent to exceptions, but I'm not sure it can be done without breaking the language. Nevertheless, I still believe that Java's biggest mistake is not checked exceptions, but the stupid distinction between primitive types and Objects (caused because all the cruft present in the latter would have make basic operations prohibitive in terms of performance, at least for early Java versions) and all the associated boxing. I have seen some extreme cases of performance degradation because of that (fortunately a refactor to use arrays solved the problem, but this is not always possible).
- mrkeen 6y ago> Functional interfaces and code that uses them should have a generic way of being transparent to exceptions This irks me too. It's been a while but I believe the workaround is just to define your own (checked) FunctionE, SupplierE, ConsumerE functional interfaces. But maybe that causes other problems I've forgotten. > the stupid distinction between primitive types and Objects I also dislike the distinction between primitives and Objects. But I don't think your argument follows. Performance is why the distinction exists. Objects have the cruft and primitives don't.
- tpxl 6y ago>This irks me too. It's been a while but I believe the workaround is just to define your own (checked) FunctionE, SupplierE, ConsumerE functional interfaces. But maybe that causes other problems I've forgotten. You can do that, but you need to do it for every exception type ): I'd love it if Java had templated exceptions. >> the stupid distinction between primitive types and Objects One of the slowest things I found out about C# was floats being objects and `a < b` (or something similar) being a stack about 7 levels deep.
- gordaco 6y agoThat's right, I would run into even bigger performance issues if I used Integer/Long/Double/etc everywhere instead of the primitive types. The problem is not that primitive types are not object, but rather, that generic code will only accept objects and not primitive types.
- ncmncm 6y agoSure and they are a big, big mistake, but biggest? I can name bigger ones. Default virtual is quite the whopper, for example.
- cletus 6y agoMethods being default virtual? Or, perhaps more accurate, being only virtual? If that's what you mean, that may well be the first time I've ever heard someone complain about this. I used Java for literally more than a decade and I never once thought "man I wish I could create a non-virtual function". This seems like some C++ pattern holdover where you obviously do have virtual vs non-virtual methods. 99% of the time I suspect it's just a source of extra bugs and the vtable lookup really isn't the performance hit (most of the time) you think it is.
- pjmlp 6y agoYou can create non-virtual methods in Java, just make them final.
- cletus 6y agoOh, so the OP really means methods being non-final by default? Yeah that makes way more sense and is a valid criticism. I don't think it's that big a deal however because, in practice, libraries that use inheritance as an external API are almost an anti-pattern at this point. I forget who said it but someone (Josh Bloch? Scott Meyer?) said at one point "inheritance is an implementation detail". Or maybe it was "inheritance breaks abstraction"? Something like that anyway. I 100% agree with this. At that point it doesn't really matter if your methods are final or not. Ideally you make your leaf classes final and move on with your life. But designing for inheritance is probably not going to end well regardless other than some notable utility classes (eg AbstractMap).
- tmccrary55 6y agoAs others have mentioned in the thread, the issue is that virtual calls are more expensive because you have to resolve the correct function in the related vtable. So instead of just setting up and executing the call, you have to traipse through memory a bunch of times, affecting caches and making things slower. But the thing is, Java's compiler works at runtime, so it can either inline the function (basically dump the code into the call site instead of invoking) or devirtualize it (if the same object is called over and over, the JIT can just remember the last lookup and add a guard if the target changes). In say C++, making everything virtual would be crazy because the compiler can only safely inline in certain situations and can rarely devirtualize a call.
- kasperni 6y agoThis is still one of the pieces written on error handling. http://joeduffyblog.com/2016/02/07/the-error-model/ http://joeduffyblog.com/2016/02/07/the-error-model/
- bcrosby95 6y agoThe biggest problem with checked exceptions is that the application writer decides what is a recoverable error. Not the library designer. I could easily imagine a program where SQLException is a recoverable error. But I don't write those types of programs.
- merb 6y agowell the problem is that SQLException is not a good exception at all. it actually includes the following errors: - database connection closed - SQLTimeoutException (o.O) - protocol exceptions like duplicated key - query exceptions the problem is that some of them are recoverable but usually it's more of an hassle and duplicated key exceptions are easier to handle in app code via upsert, etc. so basically the problems are library designer errors and not application writer errors. checked excpetions should only be exceptions that a developer COULD handle and not ones that he can't in C# the library designers usually create a "Result" object that has a boolean of succeded or status of enum instead of using exceptions for failures. i.e. most i/o errors are not recoverable, thus c# does not enforce you to recover from them.
- cletus 6y agoYeah it's reasonably common for people to try and salvage checked exceptions by saying people are doing them wrong but at some point you realize the best you're doing is trying to put lipstick on a pig. Here's a bigger problem: checked exceptions pollute your API and expose implementation details. Consider an API that stores and retrieves objects. A particular implementation does so by writing them to a database via JDBC so you get SQLExceptions. You have two basic approaches: 1. Include SQLException in your function signatures so the caller can deal with it. This exposes the implementation detail; or 2. You can hide it by transforming that checked exception into something specified for your API. At this point, what benefit have you gained from SQLException being a checked exception? You're hiding that detail. For (1), you're baking checked exceptions into your function signatures such that it can be really difficult to change later on. If you sit down and think about the practicalities the argued upsides of checked exceptions are essentially nonexistent and unchecked exceptions are actually strictly superior. Here's another pattern that happens with checked exceptions in Java: try { doSomeSQL(); } catch (SQLException e) { // do nothing } You will see people do this all the time because they don't want to deal with the checked exceptions. A better catch-clause is: throw new RuntimeException(e); You can argue people shouldn't do the first and they shouldn't but unchecked exceptions will simply bubble up unless you deliberately swallow it. That's a way better default. Defaults matter.
- hodgesrm 6y agoIf checked exceptions overall are Java's biggest mistake, then the InterruptedException implementation in particular is the second. It conflates thread management with exception handling in a way that's difficult to understand and implement correctly. The relationship between InterruptedException and the Thread.isInterrupted() method is a particular pain point for coders.
- mac01021 6y agoWhat, in your opinion, would be a better mechanism for interrupting threads in Java (aside from just making it an unchecked exception)? Something like Erlang, where any process will just die upon being sent the exit message, whether it's blocked on IO or receive or busy in the CPU, would definitely be simpler and easier to reason about. But that capability carries a runtime cost. Go's mechanism is also arguably simpler, in which goroutines are not first class objects and if you want to be able to interrupt one then you have to write ad hoc logic using a channel that you provide specifically for the purpose. But in 99.9% of cases, I think Java's more complicated mechanism with first class threads is more convenient. I would make it an unchecked exception, though. And I wish the old java.io.Socket operations and similar methods would throw InterruptedException.
- hodgesrm 6y agoThis is a really hard problem. If you use exceptions at any level they have to be generated consistently, or the solution will be what we have now. Because that's the other thing--you can't guarantee InterruptedException will even be delivered to a thread. An underlying library can just eat it or the thread could be waiting on a socket [1], etc. This kind of behavior the bane of correctness or even getting operations like clean server shutdown to work at all in some cases. So I think this really needs to be something like CSP that's built into the language in a way that makes the behavior consistent in all cases even if it introduces coding patterns that have other costs. Java _did_ get object locking and data visibility right by building in simple primitives like the synchronized keyword into the language. You can create deadlocks but the behavior is clean enough it's not hard to program around them. I would also be fine with just dying as long as there is a way to clean up shared data structures. However, that can't be manual because it's just about impossible to ensure that such cleanups are correct. Databases use transactions to get around that problem. [1] https://stackoverflow.com/questions/1024482/stop-interrupt-threads-blocked-on-waiting-input-from-socket https://stackoverflow.com/questions/1024482/stop-interrupt-t...
- specialist 6y agoHow would eliminating checked exceptions mitigate terrible API design? I've never understood the angst. All the arguments reduce down to mitigating terrible abstractions. The Correct Answer is better APIs. Mostly, that means don't pretend the network fallacies don't exist. Embrace them. Which generally means work closer to the metal. I'll say it another way. The problem is EJB, ORMs, Spring, etc. The obfuscation layers. Someone smarter than me will have to rebut the functional programming points. I'd just use a proper FP language. Multiparadigm programming is a strong second on the list of stuff you shouldn't do. (Metaprogramming is first.)
- masklinn 6y ago> How would eliminating checked exceptions mitigate terrible API design? Any API which has used checked exceptions was made worse by those, because Java's checked exceptions are bad, and their use runs actively against good APIs. So not having them would have made the corresponding APIs less bad (not necessarily good, mind) by definition.
- specialist 6y agoThank you for replying. It helps me to better articulate my thinking. You're my Rubber Duck. Why would network programming (I/O, persistence, etc) look any different whether the language was C, Java, GoLang or other? Most of my code is error checking and handling. (And now logging too, which I'll ignore here.) Is this abnormal? (Being rhetorical.) Plenty of noob code ignores errors. I sort of thought we all decided that was suboptimal. Java's response was checked exceptions. The only argument I've ever heard that made any sense is the silliness of catching exceptions so far removed from the root cause that your code can't do anything about it. So don't do that. Really, why would any one design a system that way? Because network programming is messy? Because it'd be neat to compartmentalize the messiness? I've been able to cleanly separate the value add business logic from real world messiness exactly one time. I was in control of the full stack, end to end. Imagine something like a useful BizTalk. I had been inspired by postfix. My engine would pass work to plugins, which didn't have to do any I/O of their own. My work anticipated serverless and AWS Lambda, if those programming frameworks (paradigms) were better designed. It now occurs to me that the checked exception abolitionists are advocating Happy Path Programming. The only other feasible Happy Path Programming strategy I know of is Erlang. I've only done an Erlang tutorial, nothing in prod, so this is just a guess.
- ben7799 6y agoI've been using java since Pre-1.0 and professionally since 1999 and I don't see checked exceptions as being particularly high up the list of java issues. Even with all the functional stuff they almost never seem to really create a major issue. My biggest issue with Java is just the way they've caved and constantly added new stuff that is always grafted on so it's never quite as good as a language that focuses on that programming paradigm from the start. But none of the problems in the language compare to the scale & scope of the problems caused by Java's default developer & architect culture. The culture is terrible... everything gets overcomplicated, overabstracted, etc. and you've got charismatic charlatans convincing wide swaths of developers to misuse and abuse language features in ways that have made a lot of people hate the language and have produced a lot of buggy and hyper inefficient code. Java itself doesn't have to be bloated, slow, buggy, and a massive memory hog. But the java developer community has continually made decisions to structure their java software in a way that makes that the default condition of Java systems. The way the Java language constantly gets new giant features grafted on plays into this.. everyone jumps on the latest language addition and misuses it for a few years before they come to understand it. By the time it's understood there's something new to move onto and abuse. Java became everything about C++ it was originally supposed to simplify.
- jimbob45 6y ago> My biggest issue with Java is just the way they've caved and constantly added new stuff that is always grafted on so it's never quite as good as a language that focuses on that programming paradigm from the start. Isn’t that basically the #1 issue with C++? They needed more and more features to compete with newer languages and, in the end, the language feels like a Swiss Army Knife. It’s got a tool for every situation but it’s ultimately impossible to grab and use with all those tools making getting a grip impossible.
- Guvante 6y agoI think most of C++'s additions have avoided these problems in the way Java ran into. C++ is pretty clear on the right way to do things at a given point in time. You will run into a similar problem when working with old code, you need to decide whether to abandon the new features to stay consistent, rewrite your program to make it consistent and new or do something in between and be inconsistent. The problem with C++ is that the prevalence of macros and #include make backwards compatibility effectively impossible to patch around. So you end up having to support ancient C++ code working as it was written decades ago working exactly the same. You can't even use file level flags because the object file in almost every case is going to #include old code. The compiler can't stop you from doing things the wrong way due to this. You could have other tooling that warns you that your code is making X mistake but the compiler can't enforce that or assume that you don't make that mistake due to this extreme backwards compatibility requirement. Side note: backwards compatibility in C++ is great and fundamental as it prevents fragmentation of the language between different incompatible dialects. The requirement alone doesn't fundamentally cause C++'s problems it just eliminates the easiest way to solve them. (Assuming Python 3 was easy)
- chriswarbo 6y agoI've been using Scala quite heavily recently, having mostly used Haskell for years, with distant memories of Java. Scala allows Java methods to be called, but doesn't bother with checked exceptions, which has bitten me quite a few times. My preferred style of error-handling is Option/Either, since I can implement the 'happy path' in small, pure pieces; plug them together with 'flatMap', etc.; then do error handling at the top with a 'fold' or 'match'. Exceptions break this approach; but it's easy to wrap problematic calls in 'Try' (where 'Try[T]' is equivalent to 'Either[Throwable, T]'). The problem is that Scala doesn't tell me when this is needed; it has to be gleaned from the documentation, reading the library source (if available), etc. I get that a RuntimeException could happen at any point; but to me the benefit of checked exceptions isn't to say "here's what you need to recover from", it's to say "these are very real possibilities you need to be aware of". In other words checked exceptions have the spirit of 'Either[Err, T]', but lack the polymorphism needed to make useful, generic plumbing. The article actually points this out, complaining that checked exceptions have to be handled/declared through 'all intervening code'; the same can actually be said of 'Option', or 'Either', or 'Try', etc., but the difference is that their 'intervening code' is usually calculated by the higher-order functions provided by Functor, Applicative, Monad, Traverse, etc. It's similar to many developer's first experience of Option/Maybe: manually unwrapping them, processing the contents, wrapping up the result, then doing the same for the next step, and so on. It takes a while to grok that we can just map/flatMap each of our steps on to the last (or use 'for/yield', do-notation, etc. if available). It would be nice to have a similar degree of polymorphism for checked exceptions. Until then, I'd still rather have them checked (so I can convert them to a 'Try'), rather than getting no assistance from the compiler at all!
- kevmo314 6y agoThe compiler can still help without needing to resort to checked exceptions. For example, `Either[T, Either[FileNotFoundCheckedException, RuntimeException]]` is basically isomorphic to a function that returns T or throws and has the same monad support, but can still force the developer to handle the checked exceptions. I'm not sure if it really addresses the underlying concern that the article presents though, which seems more like checked exceptions seem to be used in a way where the developer has no recourse anyways, so surfacing it through a monad or checked exception doesn't matter.
- jarym 6y agoMistake maybe but I disagree on the ‘biggest’ part. For me type erasure is a bigger issue. I get that it was done for backwards compatibility but the drawbacks imposed by that decision seem to only grow as more time passes and more new compromises have to be made.
- mac01021 6y agoWhat, in your opinion, is the biggest drawback of type erasure?
- Deadron 6y agoType erasure makes deserialization a pain. This has lead to multiple incompatible implementations of ways to indicate what a generic type contains on top of the existing type system. In practice it means using generic types on DTOs is a pita.
- nikeee 6y agoSome things that came to my mind: - List<T> doesn't "just work" with non-reference types. It needs boxing that increases memory usage and introduces stuff like ints being null. - We need special functional interfaces for non-reference types for that reason (e.g. IntConsumer). - This also affects Stream<T>, so we need IntStream etc. - A method must have a parameter of that generic type (or it has to belong to a class that has this generic type). It otherwise becomes indistinguishable during rumtime. For example, a method like ImmutableList.CreateBuilder<T>() is not possible in Java (that example is from C#'s collection types). Type erasure moslty comes into play when looking at non-reference types. For reference types, it seems to work pretty good (although it's weird that Map<String, String> will have the same runtime type as Map<Object, Object>). The last point I mentioned is not good, but no deal breaker. If generics would be like in .NET, we wouldn't have any of these restrictions. With type erasure, we ironically have to write more java code while not being able to express stuff in an abstract manner (Stream<T> is incompatible with IntStream).
- sedatk 6y agoThankfully, C# team made the right call and used reification.
- pmcollins 6y agoPossibly unpopular opinion: Java's biggest mistake, by far, was annotations that define behavior at runtime. So now we have consultingware like Spring where if something isn't working, it could because you missed an annotation somewhere, or put the right annotation in the wrong place. Which annotation? Where? Maybe you'll find out a week from now that you made a mistake, when a customer finds a bug in production. This took all of the compile-time checking goodness that you got from Java and threw it in the garbage. Now you either have to call an expensive consultancy, read books/manuals about your gigantic framework (fun!), go on forums, etc. You can't just use your coding skills. I still often use Java for my side projects because I love it without runtime annotations, but thank god for the rise of Golang. I'd rather deliver pizza than go back to the misery that is annotation-driven development in Java.
- xienze 6y agoThe alternative is heaps of XML or JSON configuration, detached from the code where it’s used. Consider that if you wanted to inject a bean in Spring XML it requires creating a bean definition that in turn defines all the beans injected into it, which in turn require their own bean definitions, etc. Then you have to declare exactly which field/method the bean is injected into. If you were developing software in the pre-annotations day you’d understand how much that sucks. The annotation approach is much better in comparison.
- doliveira 6y agoIs turning Java into a dynamic programming language really the way to go to fix this, though?
- tealpod 6y agoMy biggest complaint with Java is Generics. I am not against Generics, I don't like the way they were implmented in Java and they copy/pasted the same shit to C#.
- hrgiger 6y agoWell... I will take the bullet and confess that I do like checked exceptions. When they are not miss|over used they transfer the enough required knowledge what is to be handled. You dont need to handle? Transfer to higher levels on stack. That is a great fit in my opinion for applications designed with especially fault tolerance futures and using many external components. Not saying that design of CE is perfect, they might be too broad that doesnt tell you exact handle case, so it will leave you in the dark or in a call chain of a()->b()-c()-d() there might be cases that b and c wouldnt need to have contract in their method signature maybe compiler would decide if exception is orphaned or needs to be handled.
- narag 6y agoI just read the article in diagonal. It doesn't seem to address the reason that I hate the thing: sometimes I don't care about errors at all. Let's say I'm writing a kleenex program to explore some feature. Not nice if you make me feel all kind of boilerplate. Or maybe I just want to defer the error management to a higher level. Again busywork declaring exceptions.
- innocenat 6y agoIt has been a while since I touch Java, but I thought you could just add `throws Exception` to `main` if you don't care about error?
- winter_squirrel 6y ago
- franzwong 6y agoI also see people saying they miss checked exception because developers always forget to check the returned error code...
- grey-area 6y agoAccording to Gosling, including classes/inheritance was his biggest regret. I once attended a Java user group meeting where James Gosling (Java's inventor) was the featured speaker. During the memorable Q&A session, someone asked him: "If you could do Java over again, what would you change?" "I'd leave out classes," he replied. https://www.infoworld.com/article/2073649/why-extends-is-evil.html https://www.infoworld.com/article/2073649/why-extends-is-evi...
- Imnimo 6y agoSome checked exceptions make a lot of sense to try to catch and fix. FileNotFoundException is something my code can probably recover from - you asked for a file, it's not there, let me ask for a different file. Having a file reading method declare that it throws FileNotFoundException can be a helpful reminder to make sure you handle that possibility. But then there are other types of checked exceptions that are almost certainly unrecoverable because they happen way down in some other third party code. And then you get the endless chain of "throws" all the way back up the code base.
- karatinversion 6y agoI think there's a point to be made here about how checked exceptions interact with java's type system. FileNotFoundException is a pretty great checked exception - it's concrete enough that the caller actually has a chance of doing something useful in response. But most of the java standard library is designed with rather abstract APIs. Take java.io.Reader - it represents an arbitrary input source, so the Reader.read() method is declared to throw the very generic IOException. The subclasses don't make these any more concrete, leading to absurdities like StringReader.read() having a checked IOException.
- ragnese 6y ago> But then there are other types of checked exceptions that are almost certainly unrecoverable because they happen way down in some other third party code. And then you get the endless chain of "throws" all the way back up the code base. No you don't. When your code calls ThirdPartyAPI and it throws one of these checked exceptions that you know you can't recover from, you wrap it in a RuntimeException and rethrow. Then it's totally invisible to the rest of your code. Likewise, you should never have a method that throws 10 kidns of checked exceptions. You should be writing your own custom exceptions that wrap downstream exceptions into forms that are useful for you and your code (or people who will use your code). The biggest issue with checked exceptions is that people refuse to think through their unhappy paths.
- gmueckl 6y agoWrapping exceptions when re-throwing can be really useful. I think that this feature is often underused, especially by people who complain about checked exceptions.
- crehn 6y agoOn a slight tangent, I'd rather have only checked exceptions, so I know exactly what might throw where. Instead, I have to rely on potentially outdated Javadoc and debugging runtime exceptions in production to find them.
- throw_away 6y agoI wish that handling/rethrowing wasn't required, but that at dev-time I could just ask, hey, what's every kind of exception that could be thrown out of this expression & then decide which ones I wanted to handle at this level, if any.
- gmueckl 6y agoI think this feeds into my pet peeve that program source code shouldn't be text. The need to have everything spelled out in a readable fashion in text files is a major facilitator of boiler plate code in many situations (like long throws statements in Java). More semantic representations of source code have the potential to be more selective in their handling and display of such things. There would be more ways to show automatically deduced information or to filter out code/information that is irrelevant right now.
- Nursie 6y agoYou can ... ? Handle some, declare the method throws the others?
- throw_away 6y agoI don't want to have to write the declares part. I don't want my callers to have to do so, either. I want the nice list of unhandled exceptions the java compiler gives you, but I don't want to have to do anything about it if I'm cool with those exceptions being tossed up a level. Kinda like if everything was a RuntimeException, but I had a way to figure out what are all the subclasses of RTE that this expression could produce. Consider how Swift does exceptions. You mark methods as throwing methods, but you don't say what kinds of exceptions can be thrown. If you call a throwing method, you have to write a catch, but in order to figure out what kinds of different things can be thrown, I have to rely on documentation or on examining the source. AFAIK, there's no way to figure out all the different types of exceptions that can be thrown. I want something in the middle. I want the conciseness of swift, but the info provided by java while I'm writing. I'm no language designer, so I don't even know if that's possible, but it's the kind of pony I want.
- Reason077 6y agoThe biggest mistakes in Java are checked exceptions, NullPointerException, and primitive types. But which one of these is the worst mistake really depends on your perspective, and the mood of the day.
- sedatk 6y ago- primitive types + not having value types FTFY
- alasdair_ 6y agoI like checked exceptions. Yes, sometimes (especially in the oldest APIs when they were still figuring this stuff out), they were overused but mostly I think they encourage developers to really think about what happens in the failure case. I notice this especially with less experienced developers and remote calls - a lot of JS code I’ve reviewed in the past assumes the remote call will always work, yet Java code from the same developer will almost always correctly handle the situation, simply because the exception is explicitly required to be handled.
- Deadron 6y agoOne of the bigger problems is that exceptions are slow. Thus the recommendation is not to use them in cases where the caller could branch on. A perfect example is a missing element in a remote system. An old api would throw some sort of a Element Not Found Exception. With the addition of Optional though you have a object that is easier to use, conveys your intention better, and doesn't have any of the performance drawbacks. I think what it comes down to is that when dealing with situations that might be handled by the caller you should use some sort of result object instead of a exception. This leaves exceptions only for the cases where the result cant be handled. In which case you dont need checked exceptions.
- josefx 6y ago> A perfect example is a missing element in a remote system. An old api would throw some sort of a Element Not Found Exception. Wait, if the example involves a remote system how can the Exception be the bottleneck? Even generating the completely optional stacktrace shouldn't take that much time.
- benjiweber 6y agoNot as slow as many people think, especially with things like OmitStackTraceInFastThrow
- sreque 6y agoOmitStackTraceInFastThrow has caused so many headaches in debugging production issues, and each time now the answer has been to just disable it. The real answer is to stop trying to abuse exceptions as a form of general control flow.
- crehn 6y agoWhile we're talking about terrible decisions, can you guess what the following code will print? String s = null; switch (s) { default: System.out.println("Hey"); } Hint: it will throw NullPointerException.
- bcrosby95 6y agoDon't worry, there's a whole new set of terrible decisions with switch expressions.
- rwmj 6y agoI'm confused why you'd want it to do anything else. Perhaps you could give a more realistic example to motivate the argument for why the behaviour is wrong or inconvenient?
- shakna 6y agoI assume this is because null is false-y in many other languages, and can be used in switch/if statements and so on. Rather than having to be treated like an error.
- rovolo 6y agoIt's inconvenient because null could be considered a case switch(s) { case null: return false; case "y": return true; default: return false; } The broader issue is that Java handles null inconveniently. 1) Every object can be null, switch requires the argument to be non-null, and the type system doesn't warn you when NPE are possible. A type system which handles nullability could fail to compile if 's' is nullable. Kotlin does this, and it let's you opt-in to the NPE with some convenient syntax: switch(s!!) 2) It's inconvenient to handle null as a value. To properly handle the null case without throwing, you need to do one the following: if(s == null) { ... } else switch(s) { ... } switch(s == null ? "some-default" : s) The first way can be made more convenient if you change switch to work on nullable values. The second way is inconvenient, so people generally skip it. If you want switch to only work on non-null values, there're more convenient syntaxes to handle null, such as the 'elvis operator': switch(s ?: "some-default")
- bumblebritches5 6y agoNew languages should specify that every function returns an optional value, and a required errorcode, or even a pass/fail flag. and while I'm talking about my language design opinions. Get rid of setters and getters and public/private/protected access modifiers from structs, and simply create a tag for variables (like const for example), that says this variable can only be read by outside entities, or reverse the perspective; this variable can only be modified internally.
- devit 6y agoRust has a system equivalent to checked exceptions. However Rust, unlike Java, has a great macro system and can thus easily generate higher level exceptions wrapping the lower level ones.
- ragnese 6y agoI don't use any of the Rust error helpers. I don't mind a bit of boilerplate. I don't hate Java's checked exceptions. But I also actually craft my own Exception types when I write a Java package. I think that's the biggest mistake that devs make. In Rust you have to combine errors into composite error types. In Java you should do that.
- masklinn 6y agoRust's macro system is not what makes its "system equivalent to checked exceptions" bearable. That the language provides tools to operate on both results themselves and their content (in part because results are reified and thus normal values of the language, and in part because specific tooling like `?`) is what does that. Also that there is no issue of misclassification as in Java, because everything is a result and that's that.
- samfisher83 6y agoI liked checked exceptions. It makes you aware of what exceptions that might happen. Makes you think about how to handle it.
- nayuki 6y agoIn my eyes, Java's biggest mistake is that the byte type is signed instead of unsigned. Masking a signed byte with (b & 0xFF) causes so much needless pain, and I have never wanted to use a signed byte. On the other hand, I appreciate that Java doesn't have unsigned versions of every integer type; that simplifies things a lot. As for checked exceptions, I'm still undecided on whether they're a good or bad thing.
- deleted 6y ago[deleted]
- noisy_boy 6y agoI've found checked exceptions pretty useful. I can pass context-specific details to the exception and encapsulate the message formatting to the exception itself (helps if I'm throwing that in multiple places). They also allow me to decide the http return code based on the exception. E.g. using Spring Boot's controller advise, I can map a group of exceptions to be user errors (say, bad request) and another group to be service errors (say, internal server error) etc and don't have to worry about where the exception is being thrown from - it'll return all the details with correct return code to the user.
- josephcsible 6y agoI don't think the whole concept of checked exceptions isn't a mistake, although the way they're implemented certainly is. In my experience, the problems with them almost always stem from one of two issues: 1. Built-in exceptions that are checked but should be unchecked, IOException being the main offender (I don't mean things like FileNotFoundException; I mean the kind you can get if the OS returns -EIO) 2. Lack of exception polymorphism, preventing you from doing things like l.stream().filter(SomeClass::somePredicateThatMayThrow), even if the function that you're doing it from can throw the same exception that the predicate can I think checked exceptions would be great and nobody would hate them if those two problems were fixed.
- mumblemumble 6y agoI'm a relative newcomer to Java, and, being a newcomer, I have put some effort into exploring as many corners of the language as I can. One that's proven to be a particular puzzle is checked exceptions. But I think I finally understand them now. I quickly found that checked exceptions just do not play nice with any sort of functional-style programming, like the article describes. But the problem goes so much deeper than that. Checked exceptions are also, as far as I can tell, incompatible with an object-oriented programming style. More or less for the same reason that they interact poorly with FP. The fundamental problem is that checked exceptions don't really play nice with polymorphism or higher-order programming of any type. Which takes us to the crux of how I understand them now: Checked exceptions may not go well with FP and OOP, but they make all the sense in the world if you're doing procedural programming. There, you're not trying to create deeply nested abstractions, and you're not messing around (much) with polymorphism tricks. The code's very lexically organized, with little in the way of dependency injection or higher-order programming. When you're programming procedurally, it's fine to be exposed to the implementation details of stuff further down on the call graph, because you're the one who put it there in the first place. And that, in turn, means that checked exceptions are not really a mistake. They're just a piece of evolutionary history. Because, early on, Java wasn't really an object-oriented language. It was a deeply procedural language with some object-oriented features. It arguably still is, it's just that there's been a big cultural shift toward trying to take a more object-oriented approach since Java 5 came along and made it more practical to do so.
- sreque 6y agoI agree with the part of your post where you show that checked exceptions are bad, but IMO Java has been OO from the start. And, if by procedural code you mean code that has zero abstractions, then, yes checked exceptions aren't a problem there because their big problem is you can't abstract over them. However, I find this statement more to be an obvious tautology than a statement that checked exceptions are really useful in any situation.
- mumblemumble 6y agoMy bias there is that I'm one of those Alan Kay worshipping hard-liners who thinks there's a lot more to object-oriented programming than simply using objects. Similar to how there's more to functional programming than using first-class procedures. So yeah, it's true, Java has classes. But its culture and idioms and standard libraries and even some language features (checked exceptions, for example) are forever pushing developers toward procedural idioms. Less so now, perhaps, but intensely so in the 1990s.
- imglorp 6y agoI just want to throw a plug for the concept of Railway Oriented Programming. It can be laid on top of almost any functional-ish language that can implement some sort of Result(Ok,Err) return type. And to the OP's point, you can catch exceptions where they occur and `return Result(Err(details))`. We applied ROP with great success at a fintech where we wanted to clean up a block of business logic with many failure paths. Instead of a forest of nested conditionals or try/catch mess, there was a very simple happy path with clear handling for all the errors. Here's a good start. Ignore the language details, the concept is universal. https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/
- whitenoice 6y agoMost codebases use lombok, you can use @SneakyThrows or @SneakyThrows(SpecificException.class) - https://projectlombok.org/features/SneakyThrows https://projectlombok.org/features/SneakyThrows
- adrianmonk 6y agoThere are two kinds of exceptions: (1) those that can (and should) be handled in a meaningful way, and (2) those there's no way to handle and you should just crash. A big part of the reason people hate checked exceptions is that actually doing #1 correctly is really damn hard. It's a whole separate dimension of complexity that your design needs to tackle. A compiler that checks exceptions forces you to do it. Abruptly, if you're new to the language. It flips the floodlights on at full brightness and makes you see the full scope of the problem. It's tempting to shoot the messenger.
- captainmuon 6y agoI think checked exceptions are backwards. If you use a `throws` declaration the caller must catch it. It quickly becomes quite onerous. (Especially if you come from the philosophy that an exception often means "abort this program - unless somebody catches this". In small programs you might just want it to crash early.) And even worse, it is not exhaustive. You can always get a RuntimeException or a NullPointerException from nowhere. It would be great if they worked the other way around. Instead of forcing the caller to catch an exception, they would guarantee that no exception leaves a certain block. So you would have a function void MyFunc() onlythrows IOException { first(); second(); } And the compiler would statically guarantee that no other exception can leak out of it - because first and second have been marked `onlythrows IOException` or are "pure" and cannot throw at all. For sure you'd need an escape hatch, like Rust's "unsafe". And it would not be very useful around legacy libraries. But it would be tremendously useful if you could drop a block like neverthrows NullPointerError { ... } in your code and be sure that everything inside is null safe! I asked about this a few years ago on StackExchange [1] but so far I never heard about it anywhere else. [1] https://softwareengineering.stackexchange.com/questions/349775/different-kind-of-checked-exceptions-guarantee-to-only-throw-x https://softwareengineering.stackexchange.com/questions/3497...
- Nursie 6y agoYou shouldn't be getting NPEs at random. And if you want to protect against them, just catch them. Your "neverthrows" looks to me just like a different way of expressing try/catch
- captainmuon 6y agoIf you use try/catch, you can either swallow the exception, deal with it, or rethrow. But sometimes you don't know how to handle or rethrow an exception. Maybe you don't even know that the library code you are calling is going to throw! A "neverthrows" block would not compile if there is anything inside that can throw (even a runtime exception). Its a way of drawing a line. A library author could use it to make sure no unexpected exceptions can bubble up. And yeah, if you get NPEs at random you are making a mistake. If I could just choose to not make mistakes, I would :-D. But until then, I'd prefer the compiler to check my work. (And catching them is not an option. What do you do then? Terminate the program? Ignore them? They need to be found at compile time, not at runtime.)
- jayd16 6y agoI don't mind Java checked exceptions but of the languages in this style I think I prefer C#'s Task type. It combines a promise with a simple IsFaulted boolean and a way to rethrow the caught exception at your discretion (or it will throw if you accidentally access the result getter). You can use catch block style with typed exception handling or simple boolean checks depending on the situation and what you prefer.
- mcguire 6y agoIf the exceptions thrown by your code aren't part of the interface, then pretty much nothing is. That means strong typing is actually Java's biggest mistake. This isn't to say that checked exceptions are always used well. (What exactly am I supposed to do about an exception from .close()?)
- aazaa 6y ago> The biggest argument against “checked” exceptions is that most exceptions can’t be fixed. The simple fact is, we don’t own the code/ subsystem that broke. We can’t see the implementation, we’re not responsible for it, and can’t fix it. Here's what Oracle has to say: > Here's the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception. https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html https://docs.oracle.com/javase/tutorial/essential/exceptions... - checked exception for recoverable errors - unchecked exception for non-recoverable errors So the argument that most errors can't be recovered from is _not_ a reason to abandon checked exceptions. It's a reason to reserve checked exceptions for those cases in which recovery is likely. The main argument in this article appears to be based on a misunderstanding.
- tootie 6y agoThe argument is that an error that can be recovered from isn't an exception. It should handled with standard guards (bounds check, null check, format check, etc). There's no checked exception that can't be better solved with an if/then statement.
- nordsieck 6y ago> The argument is that an error that can be recovered from isn't an exception. It should handled with standard guards (bounds check, null check, format check, etc). There's no checked exception that can't be better solved with an if/then statement. That's a philosophical position that is largely driven by the language (and I suppose the ecosystem around it). I happen to agree with that position, so I prefer languages like rust and go over java. But I also know that if I try to fight the customs of the language I'm working in, it'll end in a lot of pain and unnecessary angst; so, if I find myself using Java, I grit my teeth and used checked exceptions.
- tootie 6y agoChecked exceptions are optional in Java. If you look at a even an long-standing enterprise platform like Spring, there's almost no checked exceptions. I always make it a policy on Java projects to not allow checked exceptions and it's never a problem. Anything in the standard lib may have to be dealt with, but I don't allow commits with any throws declaration.
- kanzenryu2 6y agoIt's easy to see that checked exceptions are bad... no other language tried to copy the idea. A worthy experiment, but that's all. What else did not get copied? Classloaders.
- ragnese 6y agoActually, PHP has checked exceptions exactly like Java
- kanzenryu2 6y agoWell, that I did not know. But I'll take it as the exception that proves the rule if only one language copied it.
- ragnese 6y agoIt's certainly not an endorsement of checked exceptions. Just an FYI. And, further, PHP is basically just copying every feature they can from Java for the last several years.
- storedbox 6y agoChecked exceptions are hardly Java's biggest mistake.
- iso8859-1 6y agoThe confusion regarding checked exceptions (which are fine, if not misused, see aazaa's answer) was made much worse by IDE's such as Eclipse, which would generate: catch (MyCheckedException e) { e.printStackTrace(); } This causes unreliable programs, since the programmer will initially only think about the successful path. Eventually, the exception will get thrown, and things will break in weird ways. They may not notice it quickly, because the stack trace will be buried in logs. Alternatively, if the IDE default has been throw new RuntimeException(e) or something similar, which would crash the program, the programmer would have noticed it more easily. Of course, the program would still be broken, but better crash hard and violently than subtly and confusingly.
- sreque 6y agoChecked exceptions are far from fine, and this has nothing to do with IDE code generation. Monads are highly cumbersome, unwieldy, and difficult to use, but Haskell programmers put up with them anyways because they get specific value from them, being able to say things like: * effect tracking * continuations * tracking which methods perform I/O * tracking errors Java checked exceptions are like monads, but worse: they are even more unwieldy and interact poorly with the rest of the language. And yet, unlike monads in a language like Haskell, they provide practically zero value for their cost. There is a reason no language since Java has copied checked exceptions as a feature, including C# which started as a direct rip-off of Java. There are better ways to encode errors into a method signature to try to force the caller to consider them than checked exceptions.
- sreque 6y agoI'm surprised how controversial this is; checked exceptions are a mistake. There is a reason C#, Go Swift, Rust, Scala, etc. don't have them, and it's not because these language authors don't know what they are doing. Checked exceptions have all the disadvantages of monads: * Checked exceptions don't compose with each other. You have to create manual witnesses of composition (methods throwing multiple exception types, or wrapping exceptions into new exceptions like a monad transformer) * Checked exceptions infect the type system, having to be copied everywhere. However, they have none of the advantages of monads, and more disadvantages besides. Java the language does not provide any facilities for abstracting over checked exceptions, and they interact terribly with any design involving higher order functions or any other higher-level abstraction. It's time for the java community to admit they got this one wrong and move on.
- Nursie 6y ago> Java the language does not provide any facilities for abstracting over checked exceptions Can you explain what you mean by this?
- sreque 6y agoI can't write a method like this: public <T> higherOrderFunction(Function<T> f) throws whatever f throws { } I can write a method that takes in a function object that throws zero checked exceptions. I can write a method that takes in a function that throws exactly one type of checked exception. I can write a method that takes in a function object that can throw two types of checked exceptions. And so on. But this involves lots of copy-pasting, which is the opposite of abstracting. Checked exceptions are not first-class citizens in the language (see https://en.wikipedia.org/wiki/First-class_citizen https://en.wikipedia.org/wiki/First-class_citizen). Unlike return values, I can't, for instance, in the general case assign the possible checked exception thrown by a method to a variable without losing type information. To do so would require sum types, a feature which most popular languages don't have. On the other hand, if I create a class like IO<T>, which represents a possible return value of T or an IOException, then that is first class in the language and I can do anything with it that I can do with any other first-class value in the language.
- desertlounger 6y agoI write a lot of Java, and like checked exceptions. The problem is rather the opposite, basically all of the concrete exception classes that you might think to use (e.g. IllegalArgumentException) are unchecked!
- flowerlad 6y agoThe biggest mistake of C# is not having checked exceptions. Your carefully written program can crash because someone modified a dependency to throw a new exception. So the only way to make your program resilient against such changes is to catch the base Exception class, which everyone agrees is wrong (because of "swallowed" exceptions). In Java the compiler alerts you if someone modifies a dependency to throw a new exception, which is good. See long discussion here: https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dlang.org https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dl...
- sedatk 6y agoArguably, a careless library developer can break your code in infinite different ways than changing the exception type, even in Java. I'll get one in a million chance of careless library developer randomly changing exception types over writing `throws` and `try/catch` statements a million times even when I don't need to handle any exceptions at all.
- bitwize 6y agoJava's biggest mistake was not including lambdas and generics from the very beginning. Checked exceptions are a feature because they force you to think about how you will handle exceptions: usually one of die, try again, or try something else. Forcing the programmer to make these decisions explicitly is a good thing.
- sedatk 6y agoC# had also adopted lambdas and generics post first release, but made all the right decisions for transition so type system isn't a mess.
- tomohawk 6y agoJava has checked exceptions, unchecked exceptions, and errors. Some checked exceptions, such as InterruptedException, should really be something else. I've very rarely seen this exception handled properly by anyone. Often, a general catch Exception will also catch this, and while the code will work just fine in most circumstances, random threads will not go away when they should. It's a mess.
- spion 6y agoThe sad bit about checked exceptions is that everyone compares any sort of error tracking to them and immediately dismisses many useful ideas Java's checked exceptions got the worst possible combination of error tracking. Its optional, so you don't even get to see if a function throws (kind of like the billion dollar null mistake) and its based on classes, which means a lot of irrelevant names leaking throughout the codebase. Like with nulls, the main value is being able to claim that a function doesn't ever throw, at all. Apple's Swift got this just right. A simpler system of "throws / doesn't throw" and with optionally polymorphic variants / unions to complement it would go much further.
- CornCobs 6y agoHaving used Java somewhat, the biggest pain points I encountered with checked exceptions was their incompatibility with highly generic code (aka streams). What if all library functions taking lambdas (e.g. map, filter) all took throwing functions as parameters (i.e. have an extra generic X argument for the exception type, like R apply(T arg) throws X) and simply rethrew those exceptions, AND have the exceptionless functions (R apply(T arg)) be a subtype of the throwing version so they are compatible? I haven't touched java in a while so I may have forgotten a thing or 2 about its type system
- mcculley 6y agoI would prefer there were just a semantic way to find out what exceptions a method might throw. I develop a lot of Java and often end up just rethrowing a checked exception as RuntimeException or AssertionError. A long time ago, I developed quite a lot of code in Ada83. Our team found having to use documentation to express what exceptions might be thrown led to many errors. I was pleased when Java came around that this was expressed directly in the function declaration. But then it became clear that it lead to a lot of boilerplate. I would like the throws keyword to just be an indication of what might be thrown and not require that I catch it.