# ASIO Error Handling: Understanding Boost.Asio's Dual-Mode Strategy

> Explore ASIO error handling with Boost.Asio's dual-mode strategy. Choose non-throwing error codes or exceptions for flexible API development.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: deep-dive
- Published: 2026-07-12

---

**ASIO employs a dual-mode error handling strategy that allows developers to choose between non-throwing error codes via `asio::error_code&` parameters or exception-based handling through `asio::system_error`, ensuring flexibility across callback, coroutine, and synchronous APIs.**

The chriskohlhoff/asio library implements a robust error handling model derived from Boost.System. This approach enables developers to select between explicit error code inspection and traditional exception handling without changing the underlying operation semantics. Understanding ASIO error handling is essential for writing reliable network applications that handle failures gracefully across different programming paradigms.

## Dual-Mode API Design: Error Codes vs Exceptions

ASIO provides every asynchronous operation in two distinct flavors. The first accepts an `asio::error_code& ec` output parameter, allowing the caller to inspect errors without stack unwinding. The second omits this parameter and throws an `asio::system_error` when operations fail. This design appears consistently throughout the library, such as in [`include/asio/write.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/write.hpp), where `async_write` overloads demonstrate both the non-throwing signature taking `error_code&` and the throwing variant returning `void`.

The non-throwing approach suits performance-critical paths where exception handling overhead is undesirable or where errors are expected control flow. The throwing overloads provide cleaner syntax for application-level code where errors are truly exceptional circumstances.

## Working with asio::error_code

### Error Enumerations in include/asio/error.hpp

The header [`include/asio/error.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/error.hpp) defines the `asio::error` namespace containing standardized error constants. These enumerators populate `asio::error_code` objects returned by failed operations. Common values include `asio::error::operation_aborted` for cancelled operations, `asio::error::eof` when connections close cleanly, and `asio::error::host_not_found` for DNS resolution failures.

Each error code carries a category obtained via `asio::error_category`, which extends `std::error_category` to provide human-readable message strings. This integration allows ASIO error codes to interoperate with standard C++ error handling facilities.

### Error Categories

ASIO defines its own error category to distinguish network-specific errors from system-level errno values. The `asio::error_category` singleton ensures that `error_code` comparisons work correctly across different error domains, while the `what()` method provides descriptive failure messages for logging.

## Exception-Based Error Handling with asio::system_error

When using the throwing overloads, ASIO raises `asio::system_error`, defined in [`include/asio/system_error.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/system_error.hpp) as an alias for `std::system_error`. This exception type encapsulates the same `asio::error_code` value that would be returned via the output parameter in non-throwing variants.

Callers can retrieve the underlying error code through the `code()` member function, enabling precise error handling without string parsing:

```cpp
try {
    asio::async_write(socket, asio::buffer(data));
} catch (const asio::system_error& e) {
    if (e.code() == asio::error::operation_aborted) {
        // Handle cancellation
    }
}

```

## Cancellation Support and operation_aborted

Modern ASIO versions integrate cancellation through the machinery defined in [`include/asio/cancellation_signal.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_signal.hpp). When an operation is cancelled via a `cancellation_signal`, the API reports this condition uniformly as `asio::error::operation_aborted`. This applies whether using callbacks, futures, or coroutines.

The cancellation state propagates through the asynchronous operation chain, ensuring that long-running operations can be terminated cleanly without resource leaks. All cancellation-related errors use the same error code mechanism, maintaining consistency with other failure modes.

## Consistent Error Handling Across Async Models

### Callback Style

In traditional callback-based code, the dual-mode pattern appears explicitly. The non-throwing variant passes `asio::error_code` as the first argument to completion handlers:

```cpp
void do_write(asio::ip::tcp::socket& sock,
              const std::string& msg,
              std::function<void(const asio::error_code&, std::size_t)> done)
{
    asio::async_write(sock,
                      asio::buffer(msg),
                      [&](const asio::error_code& ec, std::size_t bytes)
                      {
                        // ec is set to asio::error::operation_aborted if the
                        // socket was closed before the write completed.
                        done(ec, bytes);
                      });
}

```

### Coroutine Style with co_await

When using C++20 coroutines via [`include/asio/awaitable.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/awaitable.hpp), exceptions become the natural error propagation mechanism. However, the underlying implementation still sets the same `error_code` before throwing `asio::system_error`. This translation happens automatically when `co_await`ing asynchronous operations:

```cpp
asio::awaitable<void> echo(asio::ip::tcp::socket sock)
{
    try
    {
        char data[1024];
        std::size_t n = co_await sock.async_read_some(asio::buffer(data));
        co_await asio::async_write(sock, asio::buffer(data, n));
    }
    catch (const asio::system_error& e)
    {
        if (e.code() == asio::error::operation_aborted)
            std::cout << "Connection closed by peer\n";
        else
            std::cerr << "Error: " << e.what() << "\n";
    }
}

```

## Summary

- **Dual-mode flexibility**: ASIO offers both `asio::error_code&` output parameters and exception-throwing overloads for every operation, letting developers choose based on performance and style requirements.
- **Standardized error constants**: The `asio::error` enumeration in [`include/asio/error.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/error.hpp) provides portable error values including `operation_aborted`, `eof`, and `host_not_found`.
- **Exception compatibility**: `asio::system_error` (defined in [`include/asio/system_error.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/system_error.hpp)) wraps error codes in standard exceptions, accessible via the `code()` method.
- **Unified cancellation**: Cancelled operations consistently report `asio::error::operation_aborted` through the same channels as other errors, managed by `cancellation_signal` machinery.
- **API consistency**: Whether using callbacks, coroutines with `co_await`, or synchronous calls, the same error codes and semantics apply throughout the library.

## Frequently Asked Questions

### What is the difference between asio::error_code and asio::system_error?

`asio::error_code` is a lightweight value type used for non-throwing error reporting through output parameters, while `asio::system_error` is an exception type (aliasing `std::system_error`) thrown by the convenience overloads. Both carry the same error information, and you can extract an `error_code` from a caught `system_error` via the `code()` member function.

### How does ASIO handle operation cancellation?

ASIO implements cancellation through `cancellation_signal` and `cancellation_state` objects defined in [`include/asio/cancellation_signal.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_signal.hpp). When an operation is cancelled, it completes with `asio::error::operation_aborted` set in the error code, regardless of whether you are using the error code or exception-based API.

### Should I use error codes or exceptions with ASIO coroutines?

When using C++20 coroutines with `co_await`, exceptions are the natural and recommended approach. The coroutine machinery automatically translates error codes into `asio::system_error` exceptions, allowing you to use standard try-catch blocks. However, low-level library code may still prefer error codes to avoid exception overhead in hot paths.

### Where are ASIO error constants defined?

ASIO error constants reside in [`include/asio/error.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/error.hpp), which defines the `asio::error` namespace containing enumerators like `operation_aborted`, `eof`, and `host_not_found`. This header also declares the `asio::error_category` used to categorize these errors within the standard C++ error handling framework.