An HTTP & SPDY client for Android and Java applications with efficient connection pooling, interceptors, and modern protocol support
—
Request and response interception for logging, authentication, caching, and custom processing.
Observes and modifies HTTP requests/responses in processing chain.
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}Provides access to the request and allows proceeding with processing.
interface Chain {
/**
* Returns the request being processed.
* @return the Request object
*/
Request request();
/**
* Proceeds with the request processing, possibly after modification.
* @param request the request to process (original or modified)
* @return the Response from processing
* @throws IOException if processing fails
*/
Response proceed(Request request) throws IOException;
/**
* Returns the connection that will carry this request, or null if unknown.
* @return the Connection or null
*/
Connection connection();
}Basic Usage Examples:
// Logging interceptor
class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.nanoTime();
System.out.println("Sending request " + request.url());
Response response = chain.proceed(request);
long endTime = System.nanoTime();
System.out.println("Received response for " + response.request().url() +
" in " + (endTime - startTime) / 1e6d + "ms");
return response;
}
}
// Add to client
client.interceptors().add(new LoggingInterceptor());
// Authentication interceptor
client.interceptors().add(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "Bearer " + getAccessToken());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});OkHttp supports two types of interceptors with different capabilities and timing.
/**
* Application interceptors observe the full span of each call.
* They see the original request and final response.
*/
public List<Interceptor> interceptors();
/**
* Network interceptors observe each network request and response.
* They see the actual network requests including redirects and retries.
*/
public List<Interceptor> networkInterceptors();Usage Examples:
// Application interceptor - sees original request
client.interceptors().add(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
System.out.println("App interceptor: " + request.url());
return chain.proceed(request);
}
});
// Network interceptor - sees actual network requests
client.networkInterceptors().add(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
System.out.println("Network interceptor: " + request.url());
// This may be called multiple times for redirects
return chain.proceed(request);
}
});Implement various authentication schemes using interceptors.
Usage Examples:
// Bearer token authentication
class BearerTokenInterceptor implements Interceptor {
private final String token;
public BearerTokenInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request authenticated = original.newBuilder()
.header("Authorization", "Bearer " + token)
.build();
return chain.proceed(authenticated);
}
}
// API key authentication
class ApiKeyInterceptor implements Interceptor {
private final String apiKey;
public ApiKeyInterceptor(String apiKey) {
this.apiKey = apiKey;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
HttpUrl url = original.httpUrl().newBuilder()
.addQueryParameter("api_key", apiKey)
.build();
Request request = original.newBuilder()
.url(url)
.build();
return chain.proceed(request);
}
}
// Basic authentication with retry on 401
class BasicAuthInterceptor implements Interceptor {
private final String username;
private final String password;
public BasicAuthInterceptor(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Try request without auth first
Response response = chain.proceed(original);
// If 401, add auth and retry
if (response.code() == 401) {
response.body().close();
String credentials = Credentials.basic(username, password);
Request authenticated = original.newBuilder()
.header("Authorization", credentials)
.build();
return chain.proceed(authenticated);
}
return response;
}
}Modify requests before they are sent to the server.
Usage Examples:
// Add common headers
class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request modified = original.newBuilder()
.header("User-Agent", "MyApp/1.0")
.header("Accept", "application/json")
.header("X-Client-Version", "2.1.0")
.build();
return chain.proceed(modified);
}
}
// URL rewriting
class UrlRewriteInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Rewrite localhost to staging server
HttpUrl originalUrl = original.httpUrl();
HttpUrl newUrl = originalUrl;
if ("localhost".equals(originalUrl.host())) {
newUrl = originalUrl.newBuilder()
.host("staging.api.example.com")
.build();
}
Request modified = original.newBuilder()
.url(newUrl)
.build();
return chain.proceed(modified);
}
}Process and modify responses before they reach the application.
Usage Examples:
// Response caching headers
class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response original = chain.proceed(chain.request());
// Force cache for GET requests
if ("GET".equals(chain.request().method())) {
return original.newBuilder()
.header("Cache-Control", "max-age=300")
.build();
}
return original;
}
}
// Error response logging
class ErrorLoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (!response.isSuccessful()) {
System.err.printf("HTTP %d for %s: %s%n",
response.code(),
request.url(),
response.message());
// Log response body for debugging (careful with large responses)
if (response.body().contentLength() < 1024) {
String bodyString = response.body().string();
System.err.println("Error body: " + bodyString);
// Create new response with same body content
response = response.newBuilder()
.body(ResponseBody.create(response.body().contentType(), bodyString))
.build();
}
}
return response;
}
}Implement resilience patterns using interceptors.
Usage Examples:
// Simple retry interceptor
class RetryInterceptor implements Interceptor {
private final int maxRetries;
public RetryInterceptor(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
if (response != null) {
response.body().close();
}
response = chain.proceed(request);
if (response.isSuccessful() || !isRetryable(response.code())) {
break;
}
} catch (IOException e) {
lastException = e;
if (attempt == maxRetries) {
throw e;
}
}
// Wait before retry
try {
Thread.sleep(1000 * (attempt + 1)); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted during retry", ie);
}
}
if (response == null && lastException != null) {
throw lastException;
}
return response;
}
private boolean isRetryable(int code) {
return code >= 500 || code == 408 || code == 429;
}
}
// Rate limiting interceptor
class RateLimitInterceptor implements Interceptor {
private final long minIntervalMs;
private volatile long lastRequestTime = 0;
public RateLimitInterceptor(long minIntervalMs) {
this.minIntervalMs = minIntervalMs;
}
@Override
public Response intercept(Chain chain) throws IOException {
synchronized (this) {
long now = System.currentTimeMillis();
long timeSinceLastRequest = now - lastRequestTime;
if (timeSinceLastRequest < minIntervalMs) {
try {
Thread.sleep(minIntervalMs - timeSinceLastRequest);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted during rate limiting", e);
}
}
lastRequestTime = System.currentTimeMillis();
}
return chain.proceed(chain.request());
}
}Interceptors for debugging, monitoring, and performance analysis.
Usage Examples:
// Performance monitoring interceptor
class PerformanceInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.nanoTime();
Response response = chain.proceed(request);
long duration = System.nanoTime() - startTime;
long durationMs = duration / 1_000_000;
// Log slow requests
if (durationMs > 1000) {
System.out.printf("SLOW REQUEST: %s took %dms%n", request.url(), durationMs);
}
// Add timing header to response
return response.newBuilder()
.header("X-Response-Time", durationMs + "ms")
.build();
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-squareup-okhttp--okhttp