Correct error handling separates 'not found' from 'access denied' from 'network down', each needing a different response.
Errors returned by an AWS service raise `botocore.exceptions.ClientError`. The details are in e.response:
e.response["Error"]["Code"]: the AWS error code, likeNoSuchKey,AccessDenied,ThrottlingException,ConditionalCheckFailedException.e.response["Error"]["Message"]ande.response["ResponseMetadata"]["HTTPStatusCode"].- The request id in
ResponseMetadata, which AWS support asks for.
Many services also expose modeled exceptions as attributes of the client: except s3.exceptions.NoSuchKey: or except ddb.exceptions.ConditionalCheckFailedException:. They're subclasses of ClientError, so .response still works. Not every error is modeled: head_object on a missing key raises a plain ClientError with code "404", because HEAD responses have no body to carry an error code.
Problems on the client side raise subclasses of `BotoCoreError` instead: NoCredentialsError, NoRegionError, EndpointConnectionError, ReadTimeoutError, ParamValidationError (wrong parameter names or types, caught before any request is sent).
Guidelines:
- Catch specific codes you can handle, re-raise everything else.
AccessDeniedusually means an IAM policy problem: don't retry it.- Throttling and 5xx errors are retried automatically by botocore's retry handler; tune that rather than writing retry loops (see the configuration lesson).
Example
import boto3
from botocore.exceptions import ClientError, ParamValidationError
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="demo-errors-2026")
def read_config(key):
try:
return s3.get_object(Bucket="demo-errors-2026", Key=key)["Body"].read()
except s3.exceptions.NoSuchKey:
return b"{}" # sensible default
print(read_config("missing.json")) # b'{}'
try:
s3.head_object(Bucket="demo-errors-2026", Key="missing.json")
except ClientError as e:
print(e.response["Error"]["Code"]) # 404
try:
s3.get_object(Bucket="demo-errors-2026") # Key is required
except ParamValidationError as e:
print("bad params:", type(e).__name__) # bad params: ParamValidationErrorRemember
- Service errors raise ClientError; the code is in e.response['Error']['Code']
- client.exceptions.X are modeled ClientError subclasses
- head_object reports missing keys as code '404'
- BotoCoreError subclasses cover credentials, network and parameter problems
Watch out for
- Catching ClientError broadly and treating AccessDenied like 'not found'.