ProgressWorker is a SwingWorker extension, providing a fluent API for constructing background task workers for a variety of task types.

All handlers get called on the EventDispatchThread.

Note
Like SwingWorker, ProgressWorker instances can not be reused. Tasks, on the other hand, can be made stateful and reusable if required.

1. Task

// A non-progress aware task, producing no result
ProgressWorker.Task task = () -> {
  // Perform the task
};

ProgressWorker.builder()
        .task(task)
        .onException(exception ->
                Dialogs.exception()
                        .owner(applicationFrame)
                        .show(exception))
        .execute();

2. TaskHandler

// TaskHandler encapsulates the task and its handlers in a single class.
// Handler interface methods are called first, followed by
// any handlers added via the builder, in the order they were added.
// This enables a layered approach where the handler interface
// handles model-level concerns (logging, state updates) while
// builder handlers handle UI-level concerns (displaying dialogs).
ProgressWorker.TaskHandler task = new TaskHandler() {

  @Override
  public void execute() throws Exception {
    // Perform the task
  }

  // Called first on exception: log the error (model-level)
  @Override
  public void onException(Exception exception) {
    LOG.log(Level.WARNING, exception.getMessage());
  }
};

ProgressWorker.builder()
        .task(task)
        // Called after the handler's onException: display the error (UI-level)
        .onException(exception -> Dialogs.exception()
                .owner(applicationFrame)
                .show(exception))
        .execute();

3. ResultTask

// A non-progress aware task, producing a result
ProgressWorker.ResultTask<String> task = () -> {
  // Perform the task
  return "Result";
};

ProgressWorker.builder()
        .task(task)
        .onResult(result ->
                showMessageDialog(applicationFrame, result))
        .onException(exception ->
                Dialogs.exception()
                        .owner(applicationFrame)
                        .show(exception))
        .execute();

4. ResultTaskHandler

// ResultTaskHandler encapsulates a result-producing task and its handlers.
// The handler's onResult and onException are called first (model-level),
// then the builder's handlers are called after (UI-level).
ResultTaskHandler<String> task = new ResultTaskHandler<String>() {

  @Override
  public String execute() throws Exception {
    // Perform the task
    return "Result";
  }

  // Called first on success: log the result (model-level)
  @Override
  public void onResult(String result) {
    LOG.log(Level.INFO, result);
  }

  // Called first on exception: log the error (model-level)
  @Override
  public void onException(Exception exception) {
    LOG.log(Level.WARNING, exception.getMessage());
  }
};

ProgressWorker.builder()
        .task(task)
        // Called after the handler's onResult: display the result (UI-level)
        .onResult(result -> showMessageDialog(applicationFrame, result))
        // Called after the handler's onException: display the error (UI-level)
        .onException(exception -> Dialogs.exception()
                .owner(applicationFrame)
                .show(exception))
        .execute();

5. ProgressTask

// A progress aware task, producing no result
ProgressWorker.ProgressTask<String> task = progressReporter -> {
  // Perform the task
  progressReporter.report(42);
  progressReporter.publish("Message");
};

ProgressWorker.builder()
        .task(task)
        .onProgress(progress ->
                System.out.println("Progress: " + progress))
        .onPublish(message ->
                showMessageDialog(applicationFrame, message))
        .onException(exception ->
                Dialogs.exception()
                        .owner(applicationFrame)
                        .show(exception))
        .execute();

6. ProgressTaskHandler

// ProgressTaskHandler encapsulates a progress-aware task and its handlers.
// The handler's methods are called first (model-level),
// then the builder's handlers are called after (UI-level).
ProgressTaskHandler<String> task = new ProgressTaskHandler<String>() {

  @Override
  public void execute(ProgressReporter<String> progressReporter) throws Exception {
    // Perform the task
    for (int i = 0; i < maximum(); i++) {
      progressReporter.report(i);
      progressReporter.publish("Message " + i);
    }
  }

  @Override
  public void onProgress(int progress) {
    System.out.println("Progress: " + progress);
  }

  @Override
  public void onPublish(List<String> message) {
    displayMessage(message);
  }

  // Called first on exception: log the error (model-level)
  @Override
  public void onException(Exception exception) {
    LOG.log(Level.WARNING, exception.getMessage());
  }
};

ProgressWorker.builder()
        .task(task)
        // Called after the handler's onException: display the error (UI-level)
        .onException(exception -> Dialogs.exception()
                .owner(applicationFrame)
                .show(exception))
        .execute();

7. ProgressResultTask

// A reusable, cancellable task, producing a result.
// Displays a progress bar in a dialog while running.
var task = new DemoProgressResultTask();

ProgressWorker.builder()
        .task(task.prepare(142))
        .execute();
static final class DemoProgressResultTask implements ProgressResultTaskHandler<Integer, String> {

  private final JProgressBar progressBar = progressBar()
          .indeterminate(false)
          .stringPainted(true)
          .string("")
          .build();
  // Indicates whether the task has been cancelled
  private final AtomicBoolean cancelled = new AtomicBoolean();
  // A Control for setting the cancelled state
  private final Control cancel = Control.builder()
          .command(() -> cancelled.set(true))
          .caption("Cancel")
          .mnemonic('C')
          .build();
  // A panel containing the progress bar and cancel button
  private final JPanel progressPanel = borderLayoutPanel()
          .center(progressBar)
          .east(button()
                  .control(cancel))
          .build();
  // The dialog displaying the progress panel
  private final JDialog dialog = Dialogs.builder()
          .component(progressPanel)
          .owner(applicationFrame)
          // Trigger the cancel control with the Escape key
          .keyEvent(KeyEvents.builder()
                  .keyCode(VK_ESCAPE)
                  .action(cancel))
          // Prevent the dialog from closing on Escape
          .disposeOnEscape(false)
          .build();

  private int taskSize;

  @Override
  public Integer execute(ProgressReporter<String> progressReporter) throws Exception {
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < taskSize; i++) {
      Thread.sleep(50);
      if (cancelled.get()) {
        throw new CancelException();
      }
      result.add(i);
      reportProgress(progressReporter, i);
    }

    return result.stream()
            .mapToInt(Integer::intValue)
            .sum();
  }

  @Override
  public int maximum() {
    return taskSize;
  }

  @Override
  public void onStarted() {
    dialog.setVisible(true);
  }

  @Override
  public void onProgress(int progress) {
    progressBar.setValue(progress);
  }

  @Override
  public void onPublish(List<String> strings) {
    progressBar.setString(strings.get(0));
  }

  @Override
  public void onDone() {
    dialog.setVisible(false);
  }

  @Override
  public void onCancelled() {
    showMessageDialog(applicationFrame, "Cancelled");
  }

  @Override
  public void onException(Exception exception) {
    Dialogs.exception()
            .owner(applicationFrame)
            .show(exception);
  }

  @Override
  public void onResult(Integer result) {
    showMessageDialog(applicationFrame, "Result : " + result);
  }

  // Makes this task reusable by resetting the internal state
  private DemoProgressResultTask prepare(int taskSize) {
    this.taskSize = taskSize;
    progressBar.getModel().setMaximum(taskSize);
    cancelled.set(false);

    return this;
  }

  private void reportProgress(ProgressReporter<String> reporter, int progress) {
    reporter.report(progress);
    if (progress < taskSize * 0.5) {
      reporter.publish("Going strong");
    }
    else if (progress > taskSize * 0.5 && progress < taskSize * 0.85) {
      reporter.publish("Half way there");
    }
    else if (progress > taskSize * 0.85) {
      reporter.publish("Almost done");
    }
  }
}