---
title: "Errors and retries"
description: "An exception class for every kind of refusal, and retries that cannot send twice."
url: "https://openemail.uk/docs/java/errors"
area: "Java"
category: "Getting started"
---

# Errors and retries

An exception class for every kind of refusal, and retries that cannot send twice.

## Handling an error

**Errors.java**

```
try {
    client.templates().send("order-shipped", Body.of(
        "from", "dispatch@acme.com",
        "to", "ada@example.com",
        "props", Body.of("orderId", "AC-4192")
    ));
} catch (ValidationException error) {
    System.err.println(error.param() + " " + error.getMessage());
} catch (RateLimitException error) {
    System.err.println("Wait " + error.retryAfterSeconds() + " seconds");
} catch (ApiException error) {
    System.err.println(error.status() + " " + error.code() + " " + error.requestId());
} catch (NetworkException error) {
    System.err.println(error.isTimeout() ? "Timed out" : "No response");
}
```

A refusal from the API throws an `ApiException`, or the subclass for its type, with what the API said about it. Every exception the client throws is an unchecked `OpenEmailException`, so one `catch` covers them all.

| Method | What it holds |
| --- | --- |
| `status()` | The HTTP status, or 0 when no response arrived. |
| `type()`, `code()` | The error type and the code of the API, such as `validation_error` and `invalid_parameter`. |
| `param()` | The field that is to blame, when there is one. |
| `requestId()` | The id to quote when you write to support. |
| `docUrl()` | The page of the docs that explains the code. |
| `retryAfterSeconds()` | How long the API asked you to wait. |
| `fields()` | The fields a sign-up form refused, each with a `key` and an `error`. |
| `body()` | The decoded response body. |

## The kinds of failure

| Exception | When it is thrown |
| --- | --- |
| `AuthenticationException` | The key or token was refused, with a 401. |
| `PermissionException` | The credential may not do this, with a 403. |
| `NotFoundException` | Nothing has that id, with a 404. |
| `ConflictException` | The change conflicts with the current state, with a 409. |
| `ValidationException` | A field was refused, with a 422. |
| `RateLimitException` | Too many requests, or an allowance is spent, with a 429. |
| `InvalidRequestException` | Any other refusal of the request. |
| `ApiException` | The API failed, with a status of 500 or more. |
| `NetworkException` | No response arrived. |
| `IllegalArgumentException` | The call itself was wrong, and nothing was sent. |
| `WebhookSignatureException` | A webhook delivery failed its check. |

An `ApiException` also answers questions about itself: `isValidation()`, `isNotFound()`, `isRateLimited()`, `isServerError()` and `isRetryable()`, with `isScopeMissing()` when the credential lacks the scope the call needs and `isStepUpRequired()` when an access token has to verify a code first. `NetworkException.isTimeout()` says the deadline passed.

## What is tried again

- Reads, sends and every write that is safe to repeat are tried again, up to `maxRetries` times. Any other write is sent once.
- The statuses 408, 500, 502, 503 and 504 are retried with a backoff that starts at half a second and doubles up to eight seconds.
- A 429 is retried only when it carries a `Retry-After` of a minute or less, and the client waits that long.
- A connection that fails or times out is retried the same way.

> A send that is retried carries the same idempotency key every time, so the API replays the first message instead of sending a second one.
