Functional Java by Example | Part 3 ā Donāt Use Exceptions to Control Flow
This is part 3 of the series called āFunctional Java by Exampleā.
The example Iām evolving in each part of the series is some kind of āfeed handlerā which processes documents. In previous parts I started with some original code and applied some refactorings to describe āwhatā instead of āhowā.
In order to help the code going forward, we need to get rid of the good olā java.lang.Exception. (disclaimer: we canāt actually get rid of it) Thatās where this part comes in.
If you came here for the first time, itās best to start reading from the beginning. It helps to understand where we started and how we moved forward throughout the series.
These are all the parts:
- Part 1 ā From Imperative to Declarative
- Part 2 ā Tell a Story
- Part 3 ā Donāt Use Exceptions to Control Flow
- Part 4 ā Prefer Immutability
- Part 5 ā Move I/O to the Outside
- Part 6 ā Functions as Parameters
- Part 7 ā Treat Failures as Data Too
- Part 8 ā More Pure Functions
I will update the links as each article is published. If you are reading this article through content syndication please check the original articles on my blog.
Each time also the code is pushed to this GitHub project.
Getting up to speed about Exceptions
Our java.lang.Exception has been around since Java 1.0 ā and has basically been our friend in good times and nemesis at other times.
Thereās not much to talk about them, but if you want to read up on a few sources, here are my favorites:
- Exceptions in Java (JavaWorld)
- Exceptions in Java ā GeeksforGeeks (geeksforgeeks.org)
- 9 Best Practices to Handle Exceptions in Java (stackify.com)
- Best Practices for Exception Handling (onjava.com)
- Java Exception Interview Questions and Answers (journaldev.com)
- Exception handling in java with examples (beginnersbook.com)
- Java Exception Handling (Try-catch) (hackerrank.com)
- Top 20 Java Exception Handling Best Practices ā HowToDoInJava (howtodoinjava.com)
- Exception Handling & Assertion in Java ā NTU (ntu.edu.sg)
- Exception Handling: A Best Practice Guide (dzone.com)
- 9 Best Practices to Handle Exceptions in Java (dzone.com)
- Fixing 7 Common Java Exception Handling Mistakes (dzone.com)
- Java Practices -> Checked versus unchecked exceptions (javapractices.com)
- Common mistakes with exceptions in Java | Mikael StĆ„ldalās technical blog (staldal.nu)
- 11 Mistakes Java Developers Make When Using Exceptions (medium.com/@rafacdelnero)
- Are checked exceptions good or bad? (JavaWorld)
- Checked exceptions: Javaās biggest mistake | Literate Java (literatejava.com)
- Unchecked ExceptionsāThe Controversy (docs.oracle.com)
- The Trouble with Checked Exceptions (artima.com)
- Exceptions in Java: Youāre (Probably) Doing It Wrong (dzone.com)
- Java theory and practice: The exceptions debate ā IBM (ibm.com)
- Javaās checked exceptions were a mistake (and hereās what I would like to do about it (radio-weblogs.com)
- Buggy Java Code: Top 10 Most Common Mistakes That Java Developers Make | Toptal (toptal.com)
You on Java 8 already? Life became so much better! I⦠Errā¦oh, wait.
- Error handling with Java input streams ā Javamex (javamex.com)
- Handling checked exceptions in Java streams (oreilly.com)
- Exceptional Exception Handling In JDK 8 Streams (azul.com)
- Java 8 Functional Interfaces with Exceptions (slieb.org)
- Repackaging Exceptions In Streams ā blog@CodeFX (blog.codefx.org)
- How to handle Exception in Java 8 Stream? ā Stack Overflow (stackoverflow.com)
- Checked Exceptions and Streams | Benjiās Blog (benjiweber.co.uk)
- A story of Checked Exceptions and Java 8 Lambda Expressions (javadevguy.wordpress.com) ā nice war story!
- hgwood/java8-streams-and-exceptions (github.com)
- ā¦
Ok, seems that thereās no way you can actually do it right.
At least, after reading above list, weāre now completely up-to-speed on the topic ��
Luckily I donāt have to write a blog post any more about whatās been covered for 95% already in above articles, but Iāll focus here on the one Exception we actually have in the code ��
Side effects
Since youāre reading this post, youāre probably interested in why this all has to do with functional programming.
On the road to approaching your code in a more āfunctional wayā, you may have encountered the term āside effectā and that itās a ābad thingā.
In the real world, a side effect is something you did not intend to happen, and you might say itās equivalent to an āexceptionalā situation (you would indicate with an exception), but it has a more strict meaning in a Functional Programming context.
The Wikipedia-article about a Side effect says:
Side effect (computer science) In computer science, a function or expression is said to have a side effect if it modifies some state outside its scope or has an observable interaction with its calling functions or the outside world besides returning a value. ⦠In functional programming, side effects are rarely used.
So letās see how our FeedHandler code currently looks like after the first two articles in this series:
class FeedHandler {
Webservice webservice
DocumentDb documentDb
void handle(List<Doc> changes) {
changes
.findAll { doc -> isImportant(doc) }
.each { doc ->
try {
def resource = createResource(doc)
updateToProcessed(doc, resource)
} catch (e) {
updateToFailed(doc, e)
}
}
}
private Resource createResource(doc) {
webservice.create(doc)
}
private boolean isImportant(doc) {
doc.type == 'important'
}
private void updateToProcessed(doc, resource) {
doc.apiId = resource.id
doc.status = 'processed'
documentDb.update(doc)
}
private void updateToFailed(doc, e) {
doc.status = 'failed'
doc.error = e.message
documentDb.update(doc)
}
}Thereās one place where we try-catch exceptions, and thatās where we loop through the important documents and try to create a āresourceā (whatever that is) for it.
try {
def resource = createResource(doc)
updateToProcessed(doc, resource)
} catch (e) {
updateToFailed(doc, e)
}In code above catch (e) is Groovy shorthand for catch (Exception e).
Yes, thatās the generic java.lang.Exception which weāre catching. Could be any exception, including NPE.
If thereās no exception thrown from the createResource method, we update the document (ādocā) to āprocessedā, else we update it to āfailedā. BTW, even updateToProcessed can throw an exception too, but for the current discussion Iām actually only interested in a successful resource creation.
So, above code works (Iāve got the unit tests to prove it :-)) but Iām not happy with the try-catch statement as it is now. Iām only interested in successful resource creation, and, silly me, I could only come up with createResource either returning a successful resource or throwing an exception.
Throwing an exception to signal something went wrong, get the hell out of dodge, have caller catch the exception in order to handle it, is why exceptions were invented right? And itās better than returning null right?
It happens all the time. Take some of our favorite frameworks, such as EntityManager#find from the JPA spec:
Arg! Returns null.
Returns:
the found entity instance or null if the entity does not exist
Wrong example.
Functional Programming encourages side-effect free methods (or: functions), to make the code more understandable and easier to reason about. If a method just accepts certain input and returns the same output every time ā which makes it a pure function ā all kinds of optimizations can happen under the hood e.g. by the compiler, or caching, parallelisation etc.
We can replace pure functions again by their (calculated) value, which is called referential transparancy.
In previous article, weāll already extracted some logic into methods of their own, such as isImportant below. Given the same document (with the same type property) as input, weāll get the same (boolean) output every time.
boolean isImportant(doc) {
doc.type == 'important'
}Here thereās no observable side effect, no global variables are mutated, no log file is updated ā itās just stuff in, stuff out.
Thus, I would say that functions which interact with the outside world through our traditional exceptions are rarely used in functional programming.
I want to do better than that. Be better.
Optional to the rescue
As Benji Weber expresses it:
There are different viewpoints on how to use exceptions effectively in Java. Some people like checked exceptions, some argue they are a failed experiment and prefer exclusive use of unchecked exceptions. Others eschew exceptions entirely in favour of passing and returning types like Optional or Maybe.
Ok, letās try Java 8ās Optional so signal whether a resource can or can not be created.
Letās change the our webservice interface and createResource method to wrap and return our resource in an Optional:
//private Resource createResource(doc) {
private Optional<Resource> createResource(doc) {
webservice.create(doc)
}Letās change the original try-catch:
try {
def resource = createResource(doc)
updateToProcessed(doc, resource)
} catch (e) {
updateToFailed(doc, e)
}to map (processing resource) and orElseGet (processing empty optional):
createResource(doc)
.map { resource ->
updateToProcessed(doc, resource)
}
.orElseGet { /* e -> */
updateToFailed(doc, e)
}Great createResource method: either correct result comes back, or an empty result.
Wait a minute! The exception e we need to pass into updateToFailed is gone: we have an empty Optional instead. We canāt store the reason why it failed ā which we do need.
May be an Optional just signals āabsenceā and is a wrong tool for our purpose here.
Exceptional completion
Without the try-catch and with the map-orElseGet instead, I do like the way the code started to reflect the āflowā of operations more. Unfortunately, using Optional was more appropriate for āgetting somethingā or āgetting nothingā (which names like map and orElseGet also suggested) and didnāt give us the opportunity to record a reason for failing.
Whatās another way to either get the successful result or get the reason for failing, still approaching our nice way of reading?
A Future. Better yet: a CompletableFuture.
A CompletableFuture (CF) knows how to return a value , in this way itās similar to an Optional. Usually a CF is used for getting a value set in the future, but thatās not what we want to use it forā¦
From the Javadoc:
A Future that ā¦, supporting ⦠actions that trigger upon its completion.
Jip, it can signal āexceptionalā completion ā giving me the opportunity to act upon it.
Letās change the map and orElseGet:
createResource(doc)
.map { resource ->
updateToProcessed(doc, resource)
}
.orElseGet { /* e -> */
updateToFailed(doc, e)
}to thenAccept (processing success) and exceptionally (processing failure):
createResource(doc)
.thenAccept { resource ->
updateToProcessed(doc, resource)
}
.exceptionally { e ->
updateToFailed(doc, e)
}The CompletableFuture#exceptionally method accepts a function with our exception e with the actual reason for failure.
You might think: tomayto, tomahto. First we had try-catch and now we have thenAccept-exceptionally, so whatās the big difference?
Well, we can obviously not get rid of the exceptional situations, but weāre now thinking like a resident of Functionalville would: our methods start to become functions, telling us something goes in and something goes out.
Consider it a small refactoring we need towards part 4, limiting the amount of side effects in our code even more, and part 5.
This is it for now
For reference, hereās the full version of the refactored code.
class FeedHandler {
Webservice webservice
DocumentDb documentDb
void handle(List<Doc> changes) {
changes
.findAll { doc -> isImportant(doc) }
.each { doc ->
createResource(doc)
.thenAccept { resource ->
updateToProcessed(doc, resource)
}
.exceptionally { e ->
updateToFailed(doc, e)
}
}
}
private CompletableFuture<Resource> createResource(doc) {
webservice.create(doc)
}
private boolean isImportant(doc) {
doc.type == 'important'
}
private void updateToProcessed(doc, resource) {
doc.apiId = resource.id
doc.status = 'processed'
documentDb.update(doc)
}
private void updateToFailed(doc, e) {
doc.status = 'failed'
doc.error = e.message
documentDb.update(doc)
}
}ā
| Published on Java Code Geeks with permission by Ted Vinke, partner at our JCG program. See the original article here: Functional Java by Example | Part 3 ā Donāt Use Exceptions to Control Flow Opinions expressed by Java Code Geeks contributors are their own. |




