---
title: Error handling · Cloudflare Durable Objects docs
description: Any uncaught exceptions thrown by a Durable Object or thrown by
  Durable Objects' infrastructure (such as overloads or network errors) will be
  propagated to the callsite of the client. Catching these exceptions allows you
  to retry creating the DurableObjectStub and sending requests.
lastUpdated: 2025-09-29T13:29:31.000Z
chatbotDeprioritize: false
source_url:
  html: https://developers.cloudflare.com/durable-objects/best-practices/error-handling/
  md: https://developers.cloudflare.com/durable-objects/best-practices/error-handling/index.md
---

Any uncaught exceptions thrown by a Durable Object or thrown by Durable Objects' infrastructure (such as overloads or network errors) will be propagated to the callsite of the client. Catching these exceptions allows you to retry creating the [`DurableObjectStub`](https://developers.cloudflare.com/durable-objects/api/stub) and sending requests.

JavaScript Errors with the property `.retryable` set to True are suggested to be retried if requests to the Durable Object are idempotent, or can be applied multiple times without changing the response. If requests are not idempotent, then you will need to decide what is best for your application. It is strongly recommended to apply exponential backoff when retrying requests.

JavaScript Errors with the property `.overloaded` set to True should not be retried. If a Durable Object is overloaded, then retrying will worsen the overload and increase the overall error rate.

Recreating the DurableObjectStub after exceptions

Many exceptions leave the [`DurableObjectStub`](https://developers.cloudflare.com/durable-objects/api/stub) in a "broken" state, such that all attempts to send additional requests will just fail immediately with the original exception. To avoid this, you should avoid reusing a `DurableObjectStub` after it throws an exception. You should instead create a new one for any subsequent requests.

## How exceptions are thrown

Durable Objects can throw exceptions in one of two ways:

* An exception can be thrown within the user code which implements a Durable Object class. The resulting exception will have a `.remote` property set to `True` in this case.
* An exception can be generated by Durable Object's infrastructure. Some sources of infrastructure exceptions include: transient internal errors, sending too many requests to a single Durable Object, and too many requests being queued due to slow or excessive I/O (external API calls or storage operations) within an individual Durable Object. Some infrastructure exceptions may also have the `.remote` property set to `True` -- for example, when the Durable Object exceeds its memory or CPU limits.

Refer to [Troubleshooting](https://developers.cloudflare.com/durable-objects/observability/troubleshooting/) to review the types of errors returned by a Durable Object and/or Durable Objects infrastructure and how to prevent them.

## Example

This example demonstrates retrying requests using the recommended exponential backoff algorithm.

```ts
import { DurableObject } from "cloudflare:workers";


export interface Env {
  ErrorThrowingObject: DurableObjectNamespace;
}


export default {
  async fetch(request, env, ctx) {
    let userId = new URL(request.url).searchParams.get("userId") || "";


    // Retry behavior can be adjusted to fit your application.
    let maxAttempts = 3;
    let baseBackoffMs = 100;
    let maxBackoffMs = 20000;


    let attempt = 0;
    while (true) {
      // Try sending the request
      try {
        // Create a Durable Object stub for each attempt, because certain types of
        // errors will break the Durable Object stub.
        const doStub = env.ErrorThrowingObject.getByName(userId);
        const resp = await doStub.fetch("http://your-do/");


        return Response.json(resp);
      } catch (e: any) {
        if (!e.retryable) {
          // Failure was not a transient internal error, so don't retry.
          break;
        }
      }
      let backoffMs = Math.min(
        maxBackoffMs,
        baseBackoffMs * Math.random() * Math.pow(2, attempt),
      );
      attempt += 1;
      if (attempt >= maxAttempts) {
        // Reached max attempts, so don't retry.
        break;
      }
      await scheduler.wait(backoffMs);
    }
    return new Response("server error", { status: 500 });
  },
} satisfies ExportedHandler<Env>;


export class ErrorThrowingObject extends DurableObject {
  constructor(state: DurableObjectState, env: Env) {
    super(state, env);


    // Any exceptions that are raised in your constructor will also set the
    // .remote property to True
    throw new Error("no good");
  }


  async fetch(req: Request) {
    // Generate an uncaught exception
    // A .remote property will be added to the exception propagated to the caller
    // and will be set to True
    throw new Error("example error");


    // We never reach this
    return Response.json({});
  }
}
```
