boto3
ClientError
Python
AWS SDK
Exception Handling

Catching boto3 ClientError subclass

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In boto3, most AWS service failures are surfaced as botocore.exceptions.ClientError, but service clients also expose generated exception helpers such as client.exceptions.NoSuchKey for some operations. The practical rule is to use service-specific exceptions when you truly need one known case, and use ClientError plus error-code inspection when you need reliable catch-all handling.

What boto3 Actually Gives You

At the botocore layer, ClientError is the common wrapper for service-side error responses. That is why so much boto3 error handling starts there.

python
1import boto3
2from botocore.exceptions import ClientError
3
4s3 = boto3.client("s3")
5
6try:
7    s3.head_object(Bucket="example-bucket", Key="missing.txt")
8except ClientError as err:
9    code = err.response["Error"]["Code"]
10    print(code)

That pattern works across services because the response contains structured metadata.

Service-Specific Exception Helpers Also Exist

Some clients expose generated exceptions under client.exceptions. For S3, for example, you can catch NoSuchKey in targeted code paths.

python
1import boto3
2
3s3 = boto3.client("s3")
4
5try:
6    s3.get_object(Bucket="example-bucket", Key="missing.txt")
7except s3.exceptions.NoSuchKey:
8    print("object not found")

This is useful when your logic cares about one specific service-defined failure and you want a narrow branch.

The catch is that these helpers are service-specific, not a universal class hierarchy you can depend on across all AWS APIs.

When To Prefer ClientError

If you want one handler that catches all service failures for a client call, ClientError is still the right choice.

python
1from botocore.exceptions import ClientError
2
3
4def object_exists(s3, bucket, key):
5    try:
6        s3.head_object(Bucket=bucket, Key=key)
7        return True
8    except ClientError as err:
9        code = err.response.get("Error", {}).get("Code")
10        if code in {"404", "NoSuchKey", "NotFound"}:
11            return False
12        raise

This pattern is more portable because AWS services do not all describe similar failures with the same generated helper names.

Do Not Branch on the Error Message Text

Always inspect structured metadata, especially Error.Code, rather than matching human-readable messages.

python
1except ClientError as err:
2    code = err.response.get("Error", {}).get("Code")
3    status = err.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
4    print(code, status)

Message text can change, while error codes are the stable contract your code should use.

Keep Retry Logic Separate From Type Matching

A useful design is to classify errors by action, not only by exception name.

python
1TRANSIENT = {"Throttling", "RequestTimeout", "InternalError", "ServiceUnavailable"}
2
3
4def should_retry(err: ClientError) -> bool:
5    code = err.response.get("Error", {}).get("Code")
6    return code in TRANSIENT

That keeps the policy focused on business behavior. For example, AccessDenied should usually fail immediately, while Throttling may need retry with backoff.

Handle Non-Service Failures Separately

Not everything from boto3 is a ClientError. Missing credentials, endpoint connectivity issues, and similar failures occur before AWS returns a service error response.

python
1from botocore.exceptions import NoCredentialsError, EndpointConnectionError
2
3try:
4    boto3.client("s3").list_buckets()
5except NoCredentialsError:
6    print("missing AWS credentials")
7except EndpointConnectionError:
8    print("endpoint not reachable")

That is a separate category from service-side failures.

A Good Combined Pattern

In real code, a useful compromise is to catch a known generated exception first when one exact case matters, then fall back to ClientError for everything else.

python
1try:
2    s3.get_object(Bucket=bucket, Key=key)
3except s3.exceptions.NoSuchKey:
4    return None
5except ClientError as err:
6    raise RuntimeError(err.response["Error"]["Code"]) from err

That keeps the narrow business case readable without losing a safe catch-all path for the rest of the service errors.

Common Pitfalls

The biggest mistake is assuming boto3 has a universal tree of statically importable subclasses for every AWS error. It does not work that way.

Another issue is ignoring generated client-specific exceptions entirely. They can be useful in narrow, service-specific branches.

A third problem is branching on error message text instead of Error.Code, which makes handlers brittle.

Summary

  • 'ClientError is the general exception for AWS service-side failures in boto3.'
  • Some clients also expose generated helpers such as client.exceptions.NoSuchKey.
  • Use service-specific exceptions for narrow cases and ClientError for broad handling.
  • Inspect err.response["Error"]["Code"] instead of parsing message strings.
  • Handle credential and transport exceptions separately from AWS service responses.

Course illustration
Course illustration

All Rights Reserved.