Home Python httpx: Modern Async HTTP Client for Python – Complete Guide
Intermediate 3 min · July 14, 2026

httpx: Modern Async HTTP Client for Python – Complete Guide

Learn httpx, the modern async HTTP client for Python.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 20-25 min read
  • Python 3.7+ installed
  • Basic understanding of HTTP concepts
  • Familiarity with asyncio (for async examples)
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • httpx is a modern HTTP client for Python supporting both sync and async APIs.
  • It offers HTTP/2, connection pooling, timeouts, retries, and type annotations.
  • Use httpx.AsyncClient for async code and httpx.Client for sync code.
  • It's a drop-in replacement for requests with additional features.
  • Ideal for production APIs, web scraping, and microservices.
✦ Definition~90s read
What is httpx?

httpx is a modern, async-capable HTTP client for Python that supports both synchronous and asynchronous request patterns, HTTP/2, connection pooling, and robust error handling.

Imagine you're ordering food from different restaurants.
Plain-English First

Imagine you're ordering food from different restaurants. The old way (requests) is like calling each restaurant one by one and waiting on hold. httpx is like having a smart assistant that can call multiple restaurants at once, handle busy signals, and even remember your favorite orders. It's faster and more efficient.

In the world of Python HTTP clients, requests has been the go-to library for years. But as applications grow and demand for performance increases, you need a client that can handle modern requirements: async/await, HTTP/2, connection pooling, and robust error handling. Enter httpx – a fully featured HTTP client for Python 3.7+ that provides both synchronous and asynchronous APIs.

Why httpx? Imagine you're building a web scraper that fetches data from 100 APIs. With requests, you'd either block on each request or juggle threads. With httpx's async client, you can fire all 100 requests concurrently with minimal overhead. Or perhaps you're building a microservice that needs to make outbound calls without blocking the event loop – httpx integrates seamlessly with asyncio.

httpx is not just an async clone of requests. It brings native HTTP/2 support (faster multiplexing), automatic content decompression, cookie persistence, and a transport layer that you can customize for proxies or mock testing. It also supports type hints, making it a joy to use in modern Python codebases.

In this tutorial, you'll learn how to install httpx, make GET and POST requests, handle timeouts and retries, work with async clients, and debug production issues. We'll cover real-world patterns like connection pooling, streaming responses, and error handling. By the end, you'll be ready to replace requests with httpx in your next project.

Installing httpx

To get started with httpx, install it via pip. It's recommended to install the full package with HTTP/2 support. If you're working in an async environment, ensure you have anyio or asyncio available.

``bash pip install httpx ``

``bash pip install httpx[http2] ``

That's it! You're ready to make your first request.

install.pyPYTHON
1
2
3
import httpx
response = httpx.get('https://httpbin.org/get')
print(response.status_code)
Output
200
💡Virtual Environment
📊 Production Insight
In production, pin the version in your requirements.txt to avoid unexpected changes.
🎯 Key Takeaway
Install httpx with pip install httpx[http2] for full features.

Making GET and POST Requests

httpx provides a simple API similar to requests. Use httpx.get() for synchronous GET requests and httpx.post() for POST requests. You can pass query parameters, headers, and JSON data easily.

For POST requests with JSON, use the json parameter – httpx automatically sets the Content-Type header. You can also send form data with data.

basic_requests.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import httpx

# GET request
response = httpx.get('https://httpbin.org/get', params={'key': 'value'})
print(response.json())

# POST with JSON
response = httpx.post('https://httpbin.org/post', json={'name': 'Alice'})
print(response.status_code)

# POST with form data
response = httpx.post('https://httpbin.org/post', data={'field': 'value'})
print(response.text)
Output
{'args': {'key': 'value'}, ...}
200
{"form": {"field": "value"}, ...}
📊 Production Insight
Always validate response status codes with response.raise_for_status() to catch HTTP errors early.
🎯 Key Takeaway
Use params for query strings, json for JSON payloads, and data for form data.

Using the Client for Connection Pooling

For production applications, you should use httpx.Client (sync) or httpx.AsyncClient (async) instead of the top-level functions. The client maintains a connection pool, reuses TCP connections, and allows you to configure defaults like base URL, headers, and timeouts.

Using a client as a context manager ensures connections are properly closed. You can also set a base URL to avoid repeating the hostname.

client_example.pyPYTHON
1
2
3
4
5
6
7
8
9
import httpx

with httpx.Client(base_url='https://httpbin.org') as client:
    response = client.get('/get')
    print(response.status_code)
    
    # Another request reuses the same connection
    response = client.post('/post', json={'key': 'value'})
    print(response.status_code)
Output
200
200
🔥Client Reuse
📊 Production Insight
In long-running applications, avoid creating a new client per request – it defeats pooling and can cause socket exhaustion.
🎯 Key Takeaway
Always use a Client for connection pooling and shared configuration.

Async Requests with AsyncClient

One of httpx's standout features is native async support. Use httpx.AsyncClient with async/await to perform non-blocking HTTP requests. This is ideal for I/O-bound tasks like web scraping, API aggregation, or microservices.

You can run multiple requests concurrently using asyncio.gather() or asyncio.create_task(). The async client also supports context managers.

async_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import asyncio
import httpx

async def fetch(url):
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.status_code

async def main():
    urls = ['https://httpbin.org/get', 'https://httpbin.org/post']
    results = await asyncio.gather(*(fetch(url) for url in urls))
    print(results)

asyncio.run(main())
Output
[200, 200]
⚠ Event Loop
📊 Production Insight
In async web frameworks like FastAPI, reuse a single AsyncClient instance across the app to avoid connection overhead.
🎯 Key Takeaway
AsyncClient enables concurrent requests without threading overhead.

Timeouts and Retries

Production systems must handle network failures gracefully. httpx allows you to set timeouts for the entire request or individual phases (connect, read, write). You can also configure retries using a custom transport.

Timeouts can be set per-request or on the client. For retries, use httpx.HTTPTransport with the retries parameter. Note that retries only apply to certain exceptions (like connection errors) by default.

timeout_retry.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import httpx

# Timeout per request
response = httpx.get('https://httpbin.org/delay/3', timeout=5.0)

# Client-level timeout
with httpx.Client(timeout=httpx.Timeout(10.0, connect=5.0)) as client:
    response = client.get('https://httpbin.org/delay/2')

# Retries with transport
transport = httpx.HTTPTransport(retries=3)
with httpx.Client(transport=transport) as client:
    response = client.get('https://httpbin.org/status/500')  # will retry up to 3 times
Output
200
200
500 (after retries exhausted)
⚠ Retry on Idempotent Methods
📊 Production Insight
Implement exponential backoff with jitter for retries to avoid overwhelming the server.
🎯 Key Takeaway
Always set timeouts; use retries for transient failures.

Handling Errors and Exceptions

httpx raises exceptions for network errors, timeouts, and HTTP status codes (if you call raise_for_status()). Common exceptions include httpx.ConnectError, httpx.TimeoutException, and httpx.HTTPStatusError. Always wrap your requests in try-except blocks to handle failures gracefully.

You can also inspect the response for details even when an error occurs.

error_handling.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import httpx

url = 'https://httpbin.org/status/404'
try:
    response = httpx.get(url)
    response.raise_for_status()
except httpx.HTTPStatusError as e:
    print(f'HTTP error: {e.response.status_code}')
except httpx.ConnectError as e:
    print(f'Connection error: {e}')
except httpx.TimeoutException as e:
    print(f'Timeout: {e}')
except Exception as e:
    print(f'Unexpected error: {e}')
Output
HTTP error: 404
🔥Exception Hierarchy
📊 Production Insight
Log the full exception traceback and response content for debugging in production.
🎯 Key Takeaway
Use raise_for_status() and catch specific exceptions for robust error handling.

Streaming Responses and File Downloads

For large responses, you can stream the content to avoid loading everything into memory. Use client.stream() for sync or async with client.stream() for async. This is useful for downloading large files or processing real-time data.

You can iterate over the response bytes or read chunks.

streaming.pyPYTHON
1
2
3
4
5
6
import httpx

with httpx.Client() as client:
    with client.stream('GET', 'https://httpbin.org/bytes/1024') as response:
        for chunk in response.iter_bytes():
            print(f'Received {len(chunk)} bytes')
Output
Received 1024 bytes
📊 Production Insight
Always set a read timeout when streaming to prevent hanging connections.
🎯 Key Takeaway
Stream large responses to manage memory efficiently.

Custom Headers and Authentication

You can set custom headers on the client or per request. httpx supports various authentication methods: Basic, Digest, Bearer token, and custom auth classes. Use the auth parameter.

For bearer tokens, you can pass a tuple ('token', '') or use httpx.BearerAuth.

auth.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import httpx

# Custom headers
headers = {'User-Agent': 'MyApp/1.0'}
response = httpx.get('https://httpbin.org/headers', headers=headers)

# Basic auth
response = httpx.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass'))

# Bearer token
auth = httpx.BearerAuth('my-token')
response = httpx.get('https://httpbin.org/bearer', auth=auth)
print(response.status_code)
Output
200
200
200
💡Environment Variables
📊 Production Insight
For OAuth2 flows, consider using httpx.Auth subclass to automatically refresh tokens.
🎯 Key Takeaway
Use auth parameter for authentication; set default headers on the client.

Testing with Mock Transports

httpx provides a mock transport for testing. You can simulate responses without making real HTTP calls. This is perfect for unit tests. Use httpx.MockTransport and pass a handler function that returns a response.

You can also use respx library for more advanced mocking.

mock_test.pyPYTHON
1
2
3
4
5
6
7
8
import httpx

def handler(request):
    return httpx.Response(200, json={'message': 'mock'})

client = httpx.Client(transport=httpx.MockTransport(handler))
response = client.get('https://example.com/')
print(response.json())
Output
{'message': 'mock'}
🔥Integration Tests
📊 Production Insight
Combine mock transport with pytest fixtures for clean test setup.
🎯 Key Takeaway
MockTransport allows testing without network calls.
● Production incidentPOST-MORTEMseverity: high

The Silent Timeout That Took Down Our API Gateway

Symptom
Users saw 504 Gateway Timeout errors on the login page; internal latency spiked to 30 seconds.
Assumption
The upstream authentication service was slow due to high load.
Root cause
The httpx client had no timeout set, so it waited indefinitely for a response. When the auth service had a brief hiccup, all connections piled up, exhausting the connection pool.
Fix
Set a 5-second timeout on all httpx requests and implemented retries with exponential backoff.
Key lesson
  • Always set explicit timeouts on HTTP clients.
  • Monitor connection pool usage to detect leaks.
  • Use retries with jitter to avoid thundering herd.
  • Test failure scenarios in staging before production.
  • Log request durations to identify slow endpoints.
Production debug guideSymptom to Action5 entries
Symptom · 01
Requests hang indefinitely
Fix
Check if timeout is set; inspect network connectivity; use client.get(..., timeout=5).
Symptom · 02
Connection pool exhausted errors
Fix
Increase pool limits with limits=httpx.Limits(max_connections=100); ensure clients are reused.
Symptom · 03
HTTP/2 protocol errors
Fix
Force HTTP/1.1 by setting http2=False; check server compatibility.
Symptom · 04
Slow response times
Fix
Enable logging with client.get(..., timeout=httpx.Timeout(5.0, read=2.0)); profile network.
Symptom · 05
Unexpected redirects
Fix
Disable redirects with follow_redirects=False; inspect response history.
★ Quick Debug Cheat SheetCommon httpx issues and immediate fixes.
Request hangs
Immediate action
Add timeout
Commands
response = client.get(url, timeout=5)
print(response.elapsed)
Fix now
Set timeout on client: client = httpx.Client(timeout=10)
Too many connections+
Immediate action
Use context manager
Commands
async with httpx.AsyncClient() as client:
limits = httpx.Limits(max_connections=50)
Fix now
Reuse client instance across requests
HTTP/2 not working+
Immediate action
Check server support
Commands
client = httpx.Client(http2=True)
print(response.http_version)
Fix now
Fallback to HTTP/1.1: http2=False
Retries not happening+
Immediate action
Add retry transport
Commands
from httpx import TransportError
client = httpx.Client(transport=httpx.HTTPTransport(retries=3))
Fix now
Use httpx.HTTPTransport(retries=3)
Featurehttpxrequests
Async supportYes (AsyncClient)No
HTTP/2Yes (optional)No
Connection poolingBuilt-in ClientVia Session
Type hintsFull supportLimited
StreamingYesYes
TimeoutsGranular (connect, read, write)Per-request only
RetriesTransport-levelVia adapter
⚙ Quick Reference
9 commands from this guide
FileCommand / CodePurpose
install.pyresponse = httpx.get('https://httpbin.org/get')Installing httpx
basic_requests.pyresponse = httpx.get('https://httpbin.org/get', params={'key': 'value'})Making GET and POST Requests
client_example.pywith httpx.Client(base_url='https://httpbin.org') as client:Using the Client for Connection Pooling
async_example.pyasync def fetch(url):Async Requests with AsyncClient
timeout_retry.pyresponse = httpx.get('https://httpbin.org/delay/3', timeout=5.0)Timeouts and Retries
error_handling.pyurl = 'https://httpbin.org/status/404'Handling Errors and Exceptions
streaming.pywith httpx.Client() as client:Streaming Responses and File Downloads
auth.pyheaders = {'User-Agent': 'MyApp/1.0'}Custom Headers and Authentication
mock_test.pydef handler(request):Testing with Mock Transports

Key takeaways

1
httpx provides both sync and async APIs with connection pooling and HTTP/2 support.
2
Always use a Client or AsyncClient for production to reuse connections.
3
Set explicit timeouts and implement retries for resilience.
4
Handle exceptions and use raise_for_status() to catch HTTP errors.
5
Mock transports enable easy testing without network calls.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does httpx handle connection pooling?
Q02JUNIOR
Explain the difference between `httpx.get()` and using `httpx.Client().g...
Q03SENIOR
How can you implement retries with exponential backoff in httpx?
Q04SENIOR
What exceptions does httpx raise and how do you handle them?
Q05SENIOR
How do you mock HTTP requests in tests using httpx?
Q01 of 05SENIOR

How does httpx handle connection pooling?

ANSWER
httpx uses a connection pool via httpx.Client or httpx.AsyncClient. The pool reuses TCP connections for requests to the same host, reducing latency and resource usage. You can configure pool limits with Limits(max_connections=...).
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between httpx and requests?
02
How do I set a timeout in httpx?
03
Can I use httpx with asyncio?
04
Does httpx support HTTP/2?
05
How do I handle redirects in httpx?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Python Libraries. Mark it forged?

3 min read · try the examples if you haven't

Previous
FastAPI Cloud — Official Deployment Platform
58 / 69 · Python Libraries
Next
Polars: Fast DataFrame Library for Python