Home » Java » Core Java » Functional Java by Example | Part 7 – Treat Failures as Data Too

About Ted Vinke

Ted Vinke
Ted is a Java software engineer with a passion for Web development and JVM languages and works for First8, a Java Web development company in the Netherlands.

Functional Java by Example | Part 7 – Treat Failures as Data Too

This is part 7 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. We’ve already dealt with exceptional situations before, but we’re going to take care of them, more ehm,…functionally — as data.

If you came 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:

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.

Failing gracefully: small recap

This is how we left things previously:

class FeedHandler {

  List handle(List changes,
    Function creator) {

    changes
      .findAll { doc -> isImportant(doc) }
      .collect { doc ->
        creator.apply(doc)
        .thenApply { resource ->
          setToProcessed(doc, resource)
        }
        .exceptionally { e ->
          setToFailed(doc, e)
        }
        .get()
      }
  }
  
  private static boolean isImportant(doc) {
    doc.type == 'important'
  }
  
  private static Doc setToProcessed(doc, resource) {
    doc.copyWith(
      status: 'processed',
      apiId: resource.id
    )
  }
  
  private static Doc setToFailed(doc, e) {
    doc.copyWith(
      status: 'failed',
      error: e.message
    )
  }

}

Above feed handler’s primary responsibility is to “handle” a list of changed documents, which seems to be to create every time a “resource” out of a document and process it further.

This has been abstracted in previous part to a function which accepts a Doc and returns a Resource, which in Java looks like: Function creator

You can see the resource is actually wrapped in a CompletableFuture (CF), which allows us to chain method calls, such as thenApply and exceptionally. In part 3 (Don’t Use Exceptions to Control Flow) we introduced exceptionally to replace the part where we used try-catch to deal with a possible exception when creating a resource.

The code at the time looked like:

try {
  def resource = createResource(doc)
  updateToProcessed(doc, resource)
} catch (e) {
  updateToFailed(doc, e)
}

We replaced it with:

createResource(doc)
.thenAccept { resource ->
  updateToProcessed(doc, resource)
}.exceptionally { e ->
  updateToFailed(doc, e)
}

The CF allowed us to signal “exceptional” completion without using side-effects such as throwing an Exception. In the Java SDK this one of the few classes that encapsulates a result (success or failure) and shares monadic properties with e.g. an Optional (present or empty value).

Treat Failures

In other languages such a Scala there’s a dedicated type for this, called a Try.

Try

From the Scala Try docs:

The Try type represents a computation that may either result in an exception, or return a successfully computed value.

Scala developers which use Try would not need to do explicit exception handling everywhere an exception might occur. What if we were to use it in Java too?

Fortunately, there’s a library called Vavr which contains a whole lot of functional utilities we can use in our Java projects.

Example from the Vavr Try docs shows us how easy it is to forget about exceptions completely:

Try.of(() -> bunchOfWork()).getOrElse(other);

We either get the result from bunchOfWork() upon success, or other in case of failures along the way.

This class is actually an interface and has a whole bunch of default methods which all return the instance itself, which allows to chain ad infinitum, such as:

  • andFinally – Provides try’s finally behavior no matter what the result of the operation is.
  • andThen – Runs the given runnable if this is a Success, otherwise returns this Failure.
  • filter – Returns this if this is a Failure or this is a Success and the value satisfies the predicate.
  • onFailure – Consumes the throwable if this is a Failure.
  • onSuccess – Consumes the value if this is a Success.
  • map – Runs the given checked function if this is a Success, passing the result of the current expression to it.

Methods which return an ultimate value:

  • get – Gets the result of this Try if this is a Success or throws if this is a Failure.
  • getCause – Gets the cause if this is a Failure or throws if this is a Success.
  • getOrElse – Returns the underlying value if present, otherwise another value.
  • getOrElseGet – Returns the underlying value if present, otherwise a value from another Function.
  • getOrElseThrow – Returns the underlying value if present, otherwise throws supplier.get().
  • getOrElseTry – Returns the underlying value if present, otherwise returns the result of Try.of(supplier).get().
  • getOrNull – Returns the underlying value if present, otherwise null.

How can our code benefit after we’ve included the library in our project?

Just replace our CompletableFuture with Try.

Consequently, replace our calls to thenApply/exceptionally to map/getOrElseGet

creator.apply(doc)
.thenApply { resource ->
  // ...
}.exceptionally { e ->
  // ...
}.get()

becomes

creator.apply(doc)
.map { resource ->
  // ...
}.getOrElseGet { e ->
  // ...
}

The Try’s map-method accepts a function which runs when the try is a ‘success’ (as before). The getOrElseGet-method accepts a function in case of a failure e.g. an exception (as before).

You could peek inside, just as with a Stream, e.g.

creator.apply(doc)
.peek { resource ->
  println "We've got a $resource"
}
.map { resource ->
  // ...
}.getOrElseGet { e ->
  // ...
}

Or you could add some more logging for development- or troubleshooting purposes e.g.

creator.apply(doc)
.peek { resource ->
  println "We've got a $resource"
}.onSuccess { resource ->
  println "Successfully created $resource"
}.onFailure { e ->
  println "Bugger! Got a $e"
}.map { resource ->
  // ...
}.onSuccess { document ->
  println "Successfully processed $document"
}.onFailure { e ->
  println "Bugger! Processing failed with $e"
}.getOrElseGet { e ->
  // ...
}

On the surface it seems nothing has changed much. It’s just replacing one set of method calls to some others, and in this case that’s all there’s too it.

However, you may choose Try over a CompletableFuture because it might seem a more natural fit for what we want to achieve — there’s nothing “futuristic” about our computation, there’s nothing to schedule or become available “at some point in time”.

But there’s more.

Recover from failure

What we’ve got now, is that if the resource creator API fails, any failure is nicely wrapped in a Try, so we can easily follow a success- or failure-path.

But what if some of the failures have meaning to us, and in certain circumstances we want an otherwise failing scenario to succeed anyway?

Well, we can recover from failures and bend the code to our will. We can use the following method of Try, with a beautiful method signature, called recover(Class exception, Function f).

Its Javadoc reads:

Returns this, if this is a Success or this is a Failure and the cause is not assignable from cause.getClass(). Otherwise tries to recover the exception of the failure with f, i.e. calling Try.of(() -> f.apply((X) getCause()).

In other words: for a specific type of exception we can provide a function which will turn our failure into success again.

First, get rid of the superfluous logging and the onSuccess/onFailure again. Right now we have a Try, a map for the success-scenario and a getOrElseGet for the error-scenario:

class FeedHandler {
  
  List handle(List changes,
    Function creator) {

    changes
      .findAll { doc -> isImportant(doc) }
      .collect { doc ->
        creator.apply(doc)
        .map { resource ->
          setToProcessed(doc, resource)
        }.getOrElseGet { e ->
          setToFailed(doc, e)
        }
      }
  }

  // ...

}

What if the the the “resource creation” API (i.e. creator#apply call) throws e.g. a DuplicateResourceException signalling the resource we’re creating is a duplicate, it already exists.

We can use the recover function!

List handle(List changes,
    Function creator) {

    changes
      .findAll { doc -> isImportant(doc) }
      .collect { doc ->
        creator.apply(doc)
        .recover { t ->
          handleDuplicate(doc)
        }.map { resource ->
          setToProcessed(doc, resource)
        }.getOrElseGet { e ->
          setToFailed(doc, e)
        }
      }
  }

  private Resource handleDuplicate(Doc alreadyProcessed) {
    // find earlier saved, existing resource and return that one
    return repository.findById(alreadyProcessed.getApiId())
  }

We could lookup a duplicate on our side (since it’s already processed once), our “handleDuplicate” method returns whatever the happy flow expects (i.e. a Resource) and processing continues as if nothing happened.

Treat Failures

Of course, this is just an example, but recover accepts any function which accepts a Throwable and returns a Try again.

Many kinds of failure: pattern-matching

  • What if we actually need to be sure that we’re only handling our “duplicate” situation only in case of a DuplicateResourceException — and not just any exception, like now?
  • What if the API can throw another type of exception we also need to handle specifically? How can we choose between handling multiple “choices” of exception types?

This is where the pattern-matching comes in, using the Match API of Vavr. We can create a Match object for the exception x (given to use by recover) while giving the static of-method several cases to choose from.

recover { x -> Match(x).of(
  Case($(instanceOf(DuplicateResourceException.class)), t -> handleDuplicate(doc)),
  Case($(instanceOf(SpecialException.class)),  t -> handleSpecial(t))
)}

This $ is actually a static method of Vavr of which there are several overloaded versions which return a pattern.

This version here is a so-called “guard-pattern” which accepts a Predicate. Check out another example from the Vavr Javadocs (in plain Java):

String evenOrOdd(int num) {
  return Match(num).of(
    Case($(i -> i % 2 == 0), "even"),
    Case($(this::isOdd), "odd")
  );
}

boolean isOdd(int i) {
   return i % 2 == 1;
}

The combination of functions (Case, $ and Match) seem a bit strange in Java, but there’s no native support just yet. You could use Vavr for this kind of functionality in the mean time.

In Java 12 there already two preview features working hard to make all this a reality. It’s JEP 305: Pattern Matching for instanceof and JEP 325: Switch Expressions

In this instalment we have seen that we can use failures as data, e.g. take an alternative path and return back to the functional flow, so to speak.

As reference, the code now looks:

class FeedHandler {

  List<Doc> handle(List<Doc> changes,
    Function<Doc, Try<Resource>> creator) {

    changes
      .findAll { doc -> isImportant(doc) }
      .collect { doc ->
        creator.apply(doc)
        .recover { x -> Match(x).of(
          Case($(instanceOf(DuplicateResourceException.class)), t -> handleDuplicate(doc)),
          Case($(instanceOf(SpecialException.class)),  t -> handleSpecial(t))
        )}
        .map { resource ->
          setToProcessed(doc, resource)
        }.getOrElseGet { e ->
          setToFailed(doc, e)
        }
      }
  }

  private Resource handleDuplicate(Doc alreadyProcessed) {
    // find earlier saved, existing resource and return that one
    return repository.findById(alreadyProcessed.getApiId())
  }

  private Resource handleSpecial(SpecialException e) {
    // handle special situation
    return new Resource()
  }

  private static boolean isImportant(doc) {
    doc.type == 'important'
  }

  private static Doc setToProcessed(doc, resource) {
    doc.copyWith(
      status: 'processed',
      apiId: resource.id
    )
  }

  private static Doc setToFailed(doc, e) {
    doc.copyWith(
      status: 'failed',
      error: e.message
    )
  }

}

Above example on GitHub failed to parse actually properly as Groovy, since the Groovy 2.x parser didn’t understand lambda-syntax correctly, but of course you can also find the equivalent working Java version.

Go ahead, Try it yourself.

Next time, we’re concluding this series with more functions!

Treat Failures

If you have any comments or suggestions, I’d love to hear about them!

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 7 – Treat Failures as Data Too

Opinions expressed by Java Code Geeks contributors are their own.

(0 rating, 0 votes)
You need to be a registered member to rate this.
Start the discussion Views Tweet it!
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
Notify of