> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/tokio-rs/axum/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Handle errors gracefully in Axum applications

Axum provides multiple ways to handle errors in your application. Understanding these patterns will help you build robust web services.

## Basic error handling

The simplest way to handle errors is using `Result` types:

```rust theme={null}
use axum::{
    Router,
    routing::get,
    http::StatusCode,
};

async fn handler() -> Result<String, StatusCode> {
    if something_went_wrong() {
        return Err(StatusCode::INTERNAL_SERVER_ERROR);
    }
    Ok("Success".to_string())
}

fn something_went_wrong() -> bool {
    false
}

let app = Router::new()
    .route("/", get(handler));
```

Both `Ok` and `Err` variants must implement `IntoResponse`.

## Custom error types

Create application-specific error types that implement `IntoResponse`:

```rust theme={null}
use axum::{
    response::{IntoResponse, Response},
    http::StatusCode,
    Json,
};
use serde::Serialize;

#[derive(Debug)]
enum AppError {
    NotFound,
    Unauthorized,
    DatabaseError(String),
    ValidationError(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        #[derive(Serialize)]
        struct ErrorResponse {
            message: String,
        }

        let (status, message) = match self {
            AppError::NotFound => (
                StatusCode::NOT_FOUND,
                "Resource not found".to_string(),
            ),
            AppError::Unauthorized => (
                StatusCode::UNAUTHORIZED,
                "Unauthorized access".to_string(),
            ),
            AppError::DatabaseError(msg) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Database error: {}", msg),
            ),
            AppError::ValidationError(msg) => (
                StatusCode::BAD_REQUEST,
                msg,
            ),
        };

        (status, Json(ErrorResponse { message })).into_response()
    }
}

// Use in handlers
async fn get_user() -> Result<String, AppError> {
    Err(AppError::NotFound)
}
```

## Using the `?` operator

Convert errors automatically with `From` implementations:

```rust theme={null}
use axum::{
    response::{IntoResponse, Response},
    http::StatusCode,
};

#[derive(Debug)]
enum AppError {
    Database(DatabaseError),
    Validation(ValidationError),
}

#[derive(Debug)]
struct DatabaseError;

#[derive(Debug)]
struct ValidationError;

// Implement From to use ? operator
impl From<DatabaseError> for AppError {
    fn from(err: DatabaseError) -> Self {
        AppError::Database(err)
    }
}

impl From<ValidationError> for AppError {
    fn from(err: ValidationError) -> Self {
        AppError::Validation(err)
    }
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let status = match self {
            AppError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
            AppError::Validation(_) => StatusCode::BAD_REQUEST,
        };
        (status, format!("{:?}", self)).into_response()
    }
}

// Now you can use ? operator
async fn handler() -> Result<String, AppError> {
    let data = query_database()?;
    validate_data(&data)?;
    Ok(data)
}

fn query_database() -> Result<String, DatabaseError> {
    Ok("data".to_string())
}

fn validate_data(_data: &str) -> Result<(), ValidationError> {
    Ok(())
}
```

## Real-world error handling

A complete example with proper error handling:

```rust theme={null}
use axum::{
    Router,
    routing::post,
    extract::{State, Json, rejection::JsonRejection},
    response::{IntoResponse, Response},
    http::StatusCode,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

// Application errors
#[derive(Debug)]
enum AppError {
    JsonRejection(JsonRejection),
    DatabaseError(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        #[derive(Serialize)]
        struct ErrorResponse {
            message: String,
        }

        let (status, message) = match &self {
            AppError::JsonRejection(rejection) => (
                rejection.status(),
                rejection.body_text(),
            ),
            AppError::DatabaseError(_) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "Database error occurred".to_string(),
            ),
        };

        (status, Json(ErrorResponse { message })).into_response()
    }
}

impl From<JsonRejection> for AppError {
    fn from(rejection: JsonRejection) -> Self {
        Self::JsonRejection(rejection)
    }
}

// Custom JSON extractor with our error type
use axum::extract::FromRequest;

#[derive(FromRequest)]
#[from_request(via(axum::Json), rejection(AppError))]
struct AppJson<T>(T);

impl<T> IntoResponse for AppJson<T>
where
    axum::Json<T>: IntoResponse,
{
    fn into_response(self) -> Response {
        axum::Json(self.0).into_response()
    }
}

// Application state
#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

struct Database;

impl Database {
    async fn save_user(&self, _user: &CreateUser) -> Result<User, String> {
        Ok(User { id: 1, name: "Alice".to_string() })
    }
}

#[derive(Deserialize)]
struct CreateUser {
    name: String,
}

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
}

// Handler with error handling
async fn create_user(
    State(state): State<AppState>,
    AppJson(payload): AppJson<CreateUser>,
) -> Result<AppJson<User>, AppError> {
    let user = state.db
        .save_user(&payload)
        .await
        .map_err(AppError::DatabaseError)?;
    
    Ok(AppJson(user))
}

let state = AppState {
    db: Arc::new(Database),
};

let app = Router::new()
    .route("/users", post(create_user))
    .with_state(state);
```

## Error logging with middleware

Log errors without exposing details to clients:

```rust theme={null}
use axum::{
    Router,
    routing::get,
    extract::Request,
    middleware::{self, Next},
    response::{IntoResponse, Response},
    http::StatusCode,
};
use std::sync::Arc;

#[derive(Debug)]
enum AppError {
    Internal(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        // Add error to response extensions for logging
        let mut response = (
            StatusCode::INTERNAL_SERVER_ERROR,
            "Something went wrong",
        ).into_response();
        
        response.extensions_mut().insert(Arc::new(self));
        response
    }
}

// Logging middleware
async fn log_errors(
    request: Request,
    next: Next,
) -> Response {
    let response = next.run(request).await;
    
    // Check if response contains an error
    if let Some(err) = response.extensions().get::<Arc<AppError>>() {
        eprintln!("Error occurred: {:?}", err);
    }
    
    response
}

let app = Router::new()
    .route("/", get(|| async { Result::<_, AppError>::Ok("OK") }))
    .layer(middleware::from_fn(log_errors));
```

## HandleErrorLayer for middleware

Handle errors from Tower middleware:

```rust theme={null}
use axum::{
    Router,
    routing::get,
    error_handling::HandleErrorLayer,
    http::StatusCode,
    response::IntoResponse,
};
use tower::{ServiceBuilder, BoxError};
use std::{borrow::Cow, time::Duration};

async fn handle_timeout_error(err: BoxError) -> impl IntoResponse {
    if err.is::<tower::timeout::error::Elapsed>() {
        return (
            StatusCode::REQUEST_TIMEOUT,
            Cow::from("Request timed out"),
        );
    }

    if err.is::<tower::load_shed::error::Overloaded>() {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Cow::from("Service is overloaded"),
        );
    }

    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Cow::from(format!("Unhandled error: {}", err)),
    )
}

let app = Router::new()
    .route("/", get(|| async { "Hello" }))
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_timeout_error))
            .timeout(Duration::from_secs(30))
    );
```

## Extractor rejections

Customize how extractor failures are handled:

<Tabs>
  <Tab title="Custom rejection">
    ```rust theme={null}
    use axum::{
        extract::{rejection::JsonRejection, FromRequest, Request},
        response::{IntoResponse, Response},
        http::StatusCode,
    };

    struct MyJson<T>(T);

    #[axum::async_trait]
    impl<S, T> FromRequest<S> for MyJson<T>
    where
        axum::Json<T>: FromRequest<S, Rejection = JsonRejection>,
        S: Send + Sync,
    {
        type Rejection = Response;

        async fn from_request(
            req: Request,
            state: &S
        ) -> Result<Self, Self::Rejection> {
            match axum::Json::<T>::from_request(req, state).await {
                Ok(value) => Ok(Self(value.0)),
                Err(rejection) => Err((
                    StatusCode::BAD_REQUEST,
                    format!("Invalid JSON: {}", rejection),
                ).into_response()),
            }
        }
    }
    ```
  </Tab>

  <Tab title="Using derive macro">
    ```rust theme={null}
    use axum::{
        extract::{rejection::JsonRejection, FromRequest},
        response::{IntoResponse, Response},
        http::StatusCode,
    };

    #[derive(FromRequest)]
    #[from_request(via(axum::Json), rejection(ApiError))]
    struct ApiJson<T>(T);

    struct ApiError(JsonRejection);

    impl From<JsonRejection> for ApiError {
        fn from(rejection: JsonRejection) -> Self {
            Self(rejection)
        }
    }

    impl IntoResponse for ApiError {
        fn into_response(self) -> Response {
            (
                StatusCode::BAD_REQUEST,
                format!("JSON error: {}", self.0),
            ).into_response()
        }
    }
    ```
  </Tab>
</Tabs>

## Result type aliases

Simplify handler signatures with type aliases:

```rust theme={null}
use axum::{
    response::{IntoResponse, Response},
    http::StatusCode,
};

#[derive(Debug)]
struct AppError;

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        StatusCode::INTERNAL_SERVER_ERROR.into_response()
    }
}

// Create a type alias
type Result<T> = std::result::Result<T, AppError>;

// Use in handlers
async fn handler() -> Result<String> {
    Ok("Success".to_string())
}

async fn another_handler() -> Result<&'static str> {
    Err(AppError)
}
```

## Error best practices

<Accordion title="Don't expose internal errors">
  Never leak sensitive information in error messages:

  ```rust theme={null}
  // ❌ Bad - exposes database details
  impl IntoResponse for AppError {
      fn into_response(self) -> Response {
          match self {
              AppError::Database(err) => (
                  StatusCode::INTERNAL_SERVER_ERROR,
                  format!("Database error: {}", err),  // Exposes internal details!
              ).into_response(),
          }
      }
  }

  // ✅ Good - generic message for client, log details internally
  impl IntoResponse for AppError {
      fn into_response(self) -> Response {
          match self {
              AppError::Database(err) => {
                  eprintln!("Database error: {}", err);  // Log internally
                  (
                      StatusCode::INTERNAL_SERVER_ERROR,
                      "An internal error occurred",  // Generic message
                  ).into_response()
              }
          }
      }
  }
  ```
</Accordion>

<Accordion title="Use appropriate status codes">
  Return the correct HTTP status code for each error type:

  * `400 Bad Request` - Invalid input
  * `401 Unauthorized` - Missing or invalid authentication
  * `403 Forbidden` - Authenticated but not authorized
  * `404 Not Found` - Resource doesn't exist
  * `409 Conflict` - Request conflicts with current state
  * `422 Unprocessable Entity` - Valid syntax but semantic errors
  * `500 Internal Server Error` - Server errors
  * `503 Service Unavailable` - Temporary unavailability
</Accordion>

## Next steps

<CardGroup cols={2}>
  <Card title="Middleware" icon="layer-group" href="/concepts/middleware">
    Use middleware for error logging and handling
  </Card>

  <Card title="Responses" icon="arrow-up-from-bracket" href="/concepts/responses">
    Learn more about response types
  </Card>
</CardGroup>
