Monad Transformer in Java for handling Asynchronous Operations and errors
Tags: java, functionalreactiveprogramming, vavr
Published: 2024-06-25 07:50:36

## Introduction

During software engineering there is often a need to handle tasks that run in the background and might fail. Using [CompletableFuture](https://www.baeldung.com/java-completablefuture) helps with running tasks asynchronously, and [Try](https://medium.com/swlh/error-handling-made-composable-with-vavr-1684e94a6f4e) from the [Vavr](https://github.com/vavr-io/vavr) library helps manage errors in a functional way. But combining these can make the code complex. This article introduces `TryT`, a special tool that wraps Try inside `CompletableFuture`. This makes it easier to handle both asynchronous tasks and errors together.

## What is a Monad?

A monad is a pattern used in functional programming that helps manage computations and data transformations. Think of it as a wrapper around a value or a task that provides a structured way to handle operations on that value.

For example, in Java, `Optional` is a monad. It wraps a value that might or might not be there and provides methods like map to transform the value and flatMap to chain operations.

## What is a Monad Transformer?

A [monad transformer](https://en.wikipedia.org/wiki/Monad_transformer) combines two monads, allowing you to work with both at the same time without getting confused. If you have a CompletableFuture for asynchronous tasks and a Try for handling errors, a monad transformer like TryT wraps them together so you can manage both effects more easily.

## What is TryT?

TryT is a tool that combines Try and CompletableFuture. It helps you handle tasks that run in the background and might fail. TryT makes it simpler to chain these tasks and manage errors in a clean way. The name follows the naming conventions used by functional libraries in regards with monad transformers by adding a T suffix.

## Why Use TryT?

Directly working with `CompletableFuture<Try<T>>` can make your code complex and hard to read. TryT simplifies this by:

1. Combining Error and Async Handling: It handles both errors and asynchronous tasks together.
2. Cleaner Code: Makes your code easier to read and maintain.
3. Easier to Chain Tasks: Helps you chain tasks without writing a lot of extra code.

## Implementation

{% embed https://gist.github.com/VassilisSoum/cafb5211c6ca43d47b13f9c18f2d1176 %}

## Examples

### Transforming values

1. Using `CompletableFuture<Try<T>>` directly:

```java
CompletableFuture<Try<String>> futureTry = someAsyncOperation();

CompletableFuture<Try<Integer>> result = futureTry.thenApply(tryValue -> {
    return tryValue.map(String::length);
});
```
Whereas with `TryT`:

```java
TryT<String> tryT = TryT.fromFuture(someAsyncOperation());

TryT<Integer> result = tryT.map(String::length);
```

2. Chaining Asynchronous Operations

```java
CompletableFuture<Try<String>> futureTry = someAsyncOperation();

CompletableFuture<Try<Integer>> result = futureTry.thenCompose(tryValue -> {
    if (tryValue.isSuccess()) {
        return someOtherAsyncOperation(tryValue.get())
            .thenApply(Try::success)
            .exceptionally(Try::failure);
    } else {
        return CompletableFuture.completedFuture(Try.failure(tryValue.getCause()));
    }
});
```
Whereas with `TryT`

```java
TryT<String> tryT = TryT.fromFuture(someAsyncOperation());

TryT<Integer> result = tryT.flatMap(value -> TryT.fromFuture(someOtherAsyncOperation(value)));
```

3. Error recovery

```java
CompletableFuture<Try<String>> futureTry = someAsyncOperation();

CompletableFuture<Try<String>> recovered = futureTry.thenApply(tryValue -> {
    return tryValue.recover(ex -> "Fallback value");
});
```
Whereas with TryT

```java
TryT<String> tryT = TryT.fromFuture(someAsyncOperation());

TryT<String> recovered = tryT.recover(ex -> "Fallback value");
```

## Conclusion

The TryT monad transformer helps you manage asynchronous tasks and errors together in a simpler way. By combining Try with CompletableFuture, TryT provides a clean and functional approach to handle both errors and asychronous tasks. This makes your code easier to read and maintain.