> Markdown version of [Callbacks](https://vaadin.com/docs/next/building-apps/business-logic/background-jobs/interaction/callbacks). Section index: [llms.txt](https://vaadin.com/docs/next/building-apps/llms.txt)

# Callbacks

When using a Flow user interface, the simplest way of allowing background jobs to interact with it is through callbacks. You can use `Consumer`, `Runnable`, and `Supplier` as callback interfaces, depending on how you want to interact with the background job.

| Event                                | Callback              |
| ------------------------------------ | --------------------- |
| Completed without a result.          | `Runnable`            |
| Completed with a result of type `T`. | `Consumer<T>`         |
| Completed with an exception.         | `Consumer<Exception>` |
| Reported percentage done.            | `Consumer<Double>`    |
| Cancelled by user.                   | `Supplier<Boolean>`   |

The examples on this page are methods of an [application service](https://vaadin.com/docs/next/building-apps/business-logic/add-service.md) that acts as the [trigger](https://vaadin.com/docs/next/building-apps/business-logic/background-jobs/triggers.md#user-triggered-jobs) of the job. Like all application service methods, they are protected with method security. Because of this, they hand the job to an injected `TaskExecutor` instead of using the `@Async` annotation. See [User Triggered Jobs](https://vaadin.com/docs/next/building-apps/business-logic/background-jobs/triggers.md#user-triggered-jobs) for why, and for how to inject the `TaskExecutor`.

## <a id="returning-a-result"></a>Returning a Result

A background job that returns a string or an exception could be implemented like this:

```java
@PreAuthorize("hasAuthority('permission:startjob')")
public void startBackgroundJob(Consumer<String> onComplete,
                               Consumer<Exception> onError) {
    taskExecutor.execute(() -> {
        try {
            var result = doSomethingThatTakesALongTime();
            onComplete.accept(result);
        } catch (Exception ex) {
            onError.accept(ex);
        }
    });
}
```

## <a id="reporting-progress"></a>Reporting Progress

When the background job is also reporting its progress, for instance as a percentage number, it could look like this:

```java
@PreAuthorize("hasAuthority('permission:startjob')")
public void startBackgroundJob(Consumer<String> onComplete,
                               Consumer<Double> onProgress,
                               Consumer<Exception> onError) {
    taskExecutor.execute(() -> {
        try {
            onProgress.accept(0.0);

            var step1Result = performStep1();
            onProgress.accept(0.25);

            var step2Result = performStep2(step1Result);
            onProgress.accept(0.50);

            var step3Result = performStep3(step2Result);
            onProgress.accept(0.75);

            var result = performStep4(step3Result);
            onProgress.accept(1.0);

            onComplete.accept(result);
        } catch (Exception ex) {
            onError.accept(ex);
        }
    });
}
```

## <a id="cancelling"></a>Cancelling

A job can be cancelled. To do that, it would look like this:

```java
@PreAuthorize("hasAuthority('permission:startjob')")
public void startBackgroundJob(Consumer<String> onComplete,
                               Consumer<Double> onProgress,
                               Consumer<Exception> onError,
                               Supplier<Boolean> isCancelled) {
    taskExecutor.execute(() -> {
        try {
            onProgress.accept(0.0);

            if (isCancelled.get()) {
                return;
            }
            var step1Result = performStep1();
            onProgress.accept(0.25);

            if (isCancelled.get()) {
                return;
            }
            var step2Result = performStep2(step1Result);
            onProgress.accept(0.50);

            if (isCancelled.get()) {
                return;
            }
            var step3Result = performStep3(step2Result);
            onProgress.accept(0.75);

            if (isCancelled.get()) {
                return;
            }
            var result = performStep4(step3Result);
            onProgress.accept(1.0);

            onComplete.accept(result);
        } catch (Exception ex) {
            onError.accept(ex);
        }
    });
}
```

All callbacks have to be thread-safe since they’re called from the background thread, but owned and created by the user interface. For more information about how to implement these callbacks, see the [Implementing Callbacks](https://vaadin.com/docs/next/building-apps/server-push/callbacks.md) documentation page.

### <a id="improving-cancel-api"></a>Improving Cancel API

To make the cancelling API nicer, you can replace the callback with a handle. First, create a handle interface that the user interface can use to cancel the job:

```java
@FunctionalInterface
public interface CancellableJob {
    void cancel();
}
```

Next, implement the service method like this:

```java
@PreAuthorize("hasAuthority('permission:startjob')")
public CancellableJob startBackgroundJob(Consumer<String> onComplete,
                                         Consumer<Double> onProgress,
                                         Consumer<Exception> onError) {
    var cancelled = new AtomicBoolean(false);
    taskExecutor.execute(() -> {
        try {
            onProgress.accept(0.0);

            if (cancelled.get()) {
                return;
            }
            var step1Result = performStep1();
            onProgress.accept(0.25);

            if (cancelled.get()) {
                return;
            }
            var step2Result = performStep2(step1Result);
            onProgress.accept(0.50);

            if (cancelled.get()) {
                return;
            }
            var step3Result = performStep3(step2Result);
            onProgress.accept(0.75);

            if (cancelled.get()) {
                return;
            }
            var result = performStep4(step3Result);
            onProgress.accept(1.0);

            onComplete.accept(result);
        } catch (Exception ex) {
            onError.accept(ex);
        }
    });
    return () -> cancelled.set(true);
}
```

The user interface would have to store the handle while the job is running, and call the `cancel()` method to cancel it.

The handle itself is thread safe because you’re using an `AtomicBoolean`. You don’t need to take any special precautions to call it from the user interface.
