> ## Documentation Index
> Fetch the complete documentation index at: https://docs.markpdf.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Async and asynchronous jobs

> CompletableFuture and handling of 202 (auto-async) with Java's SDK.

# Async and asynchronous jobs

Java's SDK exposes two independent forms of non-blocking conversion:

1. **API asynchronous local** (`convertBytesAsync`): Your own code does not block the thread while waiting for the HTTP response, using `CompletableFuture`.
2. **Jobs due to saturation** (`202`): when all server backends are busy, the request is queued server-side and responds `202` with a `job_id`. See [`GET /jobs/{id}`](/docs/api/jobs).

Both mechanisms are independent: you can use blocking methods (`convertFile`, `convertBytes`) with `autoPoll` handling `202` internally, or use `convertBytesAsync` to not block your thread while SDK resolves everything (including polling an eventual job).

## `CompletableFuture` with `convertBytesAsync`

```java theme={null}
CompletableFuture<ConvertResult> future = client.convertBytesAsync(
    data, "report.pdf", ConvertOptions.defaults()
);

future
    .thenAccept(result -> {
        if (result instanceof ConvertResult.Markdown md) {
            System.out.println(md.markdown());
        }
    })
    .exceptionally(err -> {
        Throwable cause = err.getCause() != null ? err.getCause() : err;
        if (cause instanceof MarkpdfException.RateLimitException) {
            System.out.println("Rate limited, retry later");
        } else {
            cause.printStackTrace();
        }
        return null;
    });
```

### Componer varias conversiones en paralelo

```java theme={null}
List<Path> pdfs = List.of(Path.of("a.pdf"), Path.of("b.pdf"), Path.of("c.pdf"));

List<CompletableFuture<ConvertResult>> futures = pdfs.stream()
    .map(path -> client.convertBytesAsync(
        readAllBytesUnchecked(path), path.getFileName().toString(), ConvertOptions.defaults()))
    .toList();

CompletableFuture<Void> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

all.join(); // espera a que todas terminen
List<ConvertResult> results = futures.stream().map(CompletableFuture::join).toList();
```

<Warning>
  Aggressively parallelizing requests can trigger `429`. Limit concurrency (for example with `Semaphore` or batch processing) if you convert many documents at once.
</Warning>

## Jobs due to saturation (202)

By default, `convertFile`, `convertBytes`, `convertBytesAsync` and `convertFromUrl` handle `202` transparently (`ConvertOptions.autoPoll` is `true` by default).

```java theme={null}
// SDK waits and polls automatically (blocking)
ConvertResult result = client.convertFile(Path.of("report.pdf"), ConvertOptions.defaults());
```

### Poll manual

```java theme={null}
ConvertOptions options = ConvertOptions.builder().autoPoll(false).build();
ConvertResult result = client.convertFile(Path.of("report.pdf"), options);

if (result instanceof ConvertResult.Queued queued) {
    System.out.println("En cola: " + queued.job().jobId());

    Job finalJob = client.waitForJob(
        queued.job().jobId(),
        Duration.ofSeconds(5),
        Duration.ofMinutes(10)
    );

    if (finalJob.status() == JobStatus.COMPLETED) {
        String markdown = (String) finalJob.body();
    } else {
        throw new RuntimeException(finalJob.error());
    }
}
```

<Tip>
  Under normal load conditions you won't see `202` — it only happens when all backends are saturated. `autoPoll: true` (the default) already covers it without additional code in most cases.
</Tip>

<Warning>
  Results for completed jobs expire in approximately 1 hour. If you save a `jobId` and receive `404` when querying it, resend the original conversion.
</Warning>
