I’m a big fan of Optionals, primarily when it comes to reduction of branches or the elimination of null-checks.

But a situation that often arises when I write code is that an operation can yield one of two results:

1. A value of a type I specify
2. A message explaining why the value could not be determined

For traditional Java code, such a scenario is often expressed in methods as follows:

``````public Type determineType() throws TypeCouldNotBeDeterminedException;
``````

While this works, you’re now forced to wrap the call to this method in a try/catch block, affecting the readability of your code. What I would prefer is an approach more in line with functional programming. As it turns out, such a construct is already known in functional circles (though I am struggling to find a good source here), as the Expected monad, which contains either a result, or the exception that prevented the result from being computed.

That said, I was oblivious to the existence of this construct at the time I faced this problem, so I made my own solution, which can be found in my “personal toolbox” Hyperion, and is called TypedResult.

The primary way of using this object is as follows:

``````// Success
TypedResult.ok(result);

// Failure
TypedResult.fail("Look %s, I can use String formats!", user.getName());
``````

This works for a lot of situations I encountered, but then I started writing code like this:

``````TypedResult<String> result = determineGreeting();

if (result.isOk()) {
System.out.println(result.getObject());
}
``````

While this doesn’t look so bad, it can cause an explosion of branches similar to the one I described in my article about Optionals, especially if you need to pass the result of one method that yields a `TypedResult` into another one that also yields a `TypedResult`. To solve this, I added methods similar to the ones optional has:

``````determineGreeting().ifOk(System.out::println);
determineGreeting().throwIfNotOk(error -> new IllegalStateException("Could not determine greeting: "+ error));
determineGreeting().orElse(msg -> "Generic greeting because "+ msg);
``````

But what I really wanted was to chain them. Let’s take the following method signatures:

``````public TypedResult<T> determineT();
public R determineR(T input) throws IllegalArgumentException;
public TypedResult<V> determineV(R input);
``````

Now, assuming we want to use the result of each consecutive method as input for the next one, we would have to write something like this:

``````TypedResult<V> result;
TypedResult<T> t = determineT();

if (t.isOk()) {
try {
R r = determineR(t.getObject());

result = determineV(r);
} catch (IllegalArgumentException e) {
result = TypedResult.fail(e.getMessage());
}
} else {
result = TypedResult.fail(t.getMessage());
}
``````

Which is incredibly ugly. So instead, I created a `map` and `flatMap` operation similar to how they behave in Optional:

``````TypedResult<V> result = determineT()
.map(       t -> determineR(t)  )
.flatMap(   r -> determineV(r)  );
``````

Much, much cleaner. If any of the operations in this process fails, it will pass the error message along, while still yielding a result of the proper type to make the compiler happy.

All in all, this approach has made it much easier for me to report errors to users without having to throw exceptions through multiple application layers, it has improved the readability and reduced the number of branches for test coverage purposes.
{"ft_lang_label":"__label__en","ft_lang_prob":0.86585444,"math_prob":0.6474319,"size":3370,"snap":"2021-31-2021-39","text_gpt3_token_len":724,"char_repetition_ratio":0.13636364,"word_repetition_ratio":0.0,"special_character_ratio":0.22047478,"punctuation_ratio":0.14423077,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.95792556,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2021-09-19T20:24:03Z\",\"WARC-Record-ID\":\"<urn:uuid:00b73e85-edb6-4fa3-96cc-3d41ca37c666>\",\"Content-Length\":\"16155\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:a0fe2d7f-c7b6-495f-baff-95e08c1e17de>\",\"WARC-Concurrent-To\":\"<urn:uuid:20221014-2f3e-475f-b81e-0984659fb210>\",\"WARC-IP-Address\":\"185.199.111.153\",\"WARC-Target-URI\":\"https://tech.jeroensteenbeeke.nl/2018/01/26/when-optionals-arent-enough.html\",\"WARC-Payload-Digest\":\"sha1:G5CLKDAYE26CQ2SZASOKM7II5VLF4XKT\",\"WARC-Block-Digest\":\"sha1:FDVVAQIF6GN7UOAQTD3VKCI4HLBYES6F\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2021/CC-MAIN-2021-39/CC-MAIN-2021-39_segments_1631780056900.32_warc_CC-MAIN-20210919190128-20210919220128-00549.warc.gz\"}"}