Production Checklist
Deploying MochaJSON v1.0.0 in production? Follow this checklist to ensure your HTTP client is configured for reliability, performance, and security with the new stateless library design.
v1.0.0 Stateless Design Best Practices
✅ DO
-
Create ApiClient instances as needed
// ✅ Good - Create clients when you need them
public class UserService {
private final ApiClient client;
public UserService() {
this.client = new ApiClient.Builder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}
} -
Use static Api for simple, production-safe calls
// ✅ Good - Production-safe by default
Map<String, Object> data = Api.get("https://api.example.com/data")
.execute()
.toMap(); -
Create separate clients for different APIs
// ✅ Good - Different clients for different needs
ApiClient externalAPI = new ApiClient.Builder()
.connectTimeout(Duration.ofSeconds(30))
.allowLocalhost(false) // Production-safe
.build();
ApiClient internalAPI = new ApiClient.Builder()
.allowLocalhost(true) // Development-friendly
.connectTimeout(Duration.ofSeconds(5))
.build(); -
Configure security explicitly per client
// ✅ Good - Explicit security configuration
ApiClient prodClient = new ApiClient.Builder()
.allowLocalhost(false) // Production-safe
.build();
ApiClient devClient = new ApiClient.Builder()
.allowLocalhost(true) // Development-friendly
.build(); -
Use dependency injection for client management
// ✅ Good - Spring Boot example
@Configuration
public class ApiConfig {
@Bean
public ApiClient externalApiClient() {
return new ApiClient.Builder()
.connectTimeout(Duration.ofSeconds(10))
.allowLocalhost(false)
.build();
}
}
❌ DON'T
-
Try to shutdown the library (not needed)
// ❌ Bad - v1.0.0 doesn't have shutdown
// Api.shutdown(); // This method doesn't exist -
Worry about resource cleanup (library is stateless)
// ❌ Bad - No cleanup needed
// client.cleanup(); // Not needed - library is stateless -
Share clients unless thread-safety is guaranteed
// ❌ Bad - Sharing mutable clients
// private static ApiClient sharedClient; // Don't share mutable state
// ✅ Good - Each service has its own client
public class UserService {
private final ApiClient client; // Instance variable, not static
} -
Use global configuration (removed in v1.0.0)
// ❌ Bad - Global configuration removed
// Utils.setDefaultSecurityConfig(config); // This method doesn't exist
// ✅ Good - Per-client configuration
ApiClient client = new ApiClient.Builder()
.allowLocalhost(true)
.build();
Configuration Examples
Production Configuration
ApiClient prodClient = new ApiClient.Builder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.enableRetry()
.allowLocalhost(false) // Production-safe
.requestInterceptor(req -> {
req.header("Authorization", "Bearer " + getToken());
req.header("User-Agent", "MyApp/1.0");
return req;
})
.responseInterceptor(resp -> {
if (resp.code() >= 400) {
logError("API Error", resp.code(), resp.body());
}
return resp;
})
.build();
Development Configuration
ApiClient devClient = new ApiClient.Builder()
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(15))
.allowLocalhost(true) // Allow localhost
.enableLogging()
.build();
Test Configuration
ApiClient testClient = new ApiClient.Builder()
.connectTimeout(Duration.ofSeconds(1))
.readTimeout(Duration.ofSeconds(5))
.allowLocalhost(true)
.build();
✅ Configuration Checklist
Timeout Configuration
ApiClient client = new ApiClient.Builder()
// ✅ Set appropriate timeouts
.connectTimeout(Duration.ofSeconds(10)) // Connection establishment
.readTimeout(Duration.ofSeconds(30)) // Response reading
.writeTimeout(Duration.ofSeconds(15)) // Request writing
.build();
Why: Prevents hanging requests and resource exhaustion.
Connection Pooling
// ✅ Connection pooling is handled automatically by Java HttpClient
// No configuration needed - the underlying HttpClient manages connections efficiently
Why: Java HttpClient automatically handles connection pooling, reducing latency and improving throughput.
Security Configuration
ApiClient client = new ApiClient.Builder()
// ✅ Use production-safe security settings (blocks localhost by default)
.allowLocalhost(false) // Block localhost URLs in production
.build();
Why: Prevents SSRF attacks by blocking localhost and private IP access.
Retry Configuration
ApiClient client = new ApiClient.Builder()
// ✅ Enable simple retry for transient failures
.enableRetry() // 3 attempts with 1-second delay
// Or customize:
.enableRetry(5, Duration.ofSeconds(2)) // 5 attempts with 2-second delay
.build();
Why: Handles temporary network issues and server errors automatically.
Resource Management
// ✅ Always use try-with-resources for streams
try (ManagedInputStream stream = client.get(url).downloadStream()) {
byte[] data = stream.readAllBytes();
// Process data
} // Stream is automatically closed
// ✅ For development, allow localhost access
ApiClient devClient = new ApiClient.Builder()
.allowLocalhost(true)
.build();
// ✅ For production, use strict security
ApiClient prodClient = new ApiClient.Builder()
.allowLocalhost(false)
.build();
Why: Prevents memory leaks and ensures proper resource cleanup.
✅ Error Handling Checklist
Comprehensive Exception Handling
public class ApiService {
private final ApiClient client;
public User getUser(String id) {
try {
return client.get("/api/users/" + id)
.execute()
.to(User.class);
} catch (ApiException e) {
// ✅ Handle HTTP errors appropriately
switch (e.getStatusCode()) {
case 404:
throw new UserNotFoundException("User not found: " + id);
case 429:
throw new RateLimitExceededException("Rate limit exceeded");
case 500:
case 502:
case 503:
throw new ServiceUnavailableException("Service temporarily unavailable");
default:
throw new ApiErrorException("API error: " + e.getStatusCode());
}
} catch (JsonException e) {
// ✅ Handle JSON parsing errors
logger.error("Failed to parse JSON response", e);
throw new DataProcessingException("Invalid response format");
} catch (Exception e) {
// ✅ Handle unexpected errors
logger.error("Unexpected error in getUser", e);
throw new ServiceException("Internal error");
}
}
}
Graceful Degradation
public class ResilientApiService {
private final ApiClient primaryClient;
private final ApiClient fallbackClient;
public User getUser(String id) {
try {
// ✅ Try primary service first
return primaryClient.get("/api/users/" + id)
.execute()
.to(User.class);
} catch (ApiException e) {
if (e.getStatusCode() >= 500) {
// ✅ Fallback to secondary service
logger.warn("Primary service failed, using fallback", e);
return fallbackClient.get("/api/users/" + id)
.execute()
.to(User.class);
}
throw e;
}
}
}
✅ Logging Configuration
Request/Response Logging
ApiClient client = new ApiClient.Builder()
// ✅ Enable structured logging
.enableLogging()
.build();
Custom Logging Interceptors
// ✅ Custom logging for production monitoring
RequestInterceptor requestLogger = request -> {
logger.info("API Request: {} {}", request.getMethod(), request.getUrl());
return request.header("X-Request-ID", UUID.randomUUID().toString());
};
ResponseInterceptor responseLogger = response -> {
logger.info("API Response: {} - {}ms",
response.code(),
response.getDuration().toMillis());
return response;
};
ApiClient client = new ApiClient.Builder()
.addRequestInterceptor(requestLogger)
.addResponseInterceptor(responseLogger)
.build();
Log Levels
# logback.xml or application.yml
logging:
level:
io.mochaapi.client: INFO # ✅ Production level
# io.mochaapi.client: DEBUG # ❌ Only for debugging
✅ Security Checklist
Authentication
ApiClient client = new ApiClient.Builder()
// ✅ Use secure authentication
.addRequestInterceptor(RequestInterceptor.bearerAuth(() -> getSecureToken()))
.build();
// ✅ Token refresh mechanism
private String getSecureToken() {
if (token == null || token.isExpired()) {
token = refreshToken();
}
return token.getValue();
}
URL Validation
// ✅ MochaJSON automatically validates URLs
// These will throw IllegalArgumentException:
// client.get("javascript:alert('xss')").execute(); // ❌ Rejected
// client.get("file:///etc/passwd").execute(); // ❌ Rejected
// ✅ Only these are allowed:
client.get("https://api.example.com/data").execute(); // ✅ OK
client.get("http://localhost:8080/api").execute(); // ✅ OK (local)
Input Sanitization
public User getUser(String id) {
// ✅ Validate and sanitize input
if (id == null || id.trim().isEmpty()) {
throw new IllegalArgumentException("User ID cannot be null or empty");
}
// ✅ Sanitize the ID
String sanitizedId = id.trim().replaceAll("[^a-zA-Z0-9-_]", "");
return client.get("/api/users/" + sanitizedId)
.execute()
.to(User.class);
}
✅ Performance Checklist
Performance Monitoring
ApiClient client = new ApiClient.Builder()
// ✅ Enable logging for performance monitoring
.enableLogging()
.build();
Async Operations
// ✅ Use async for non-blocking operations
public CompletableFuture<List<User>> getUsersAsync() {
return client.get("/api/users")
.executeAsync()
.thenApply(response -> response.toList());
}
// ✅ Proper exception handling in async
public CompletableFuture<User> getUserAsync(String id) {
return client.get("/api/users/" + id)
.executeAsync()
.thenApply(response -> response.to(User.class))
.exceptionally(throwable -> {
logger.error("Failed to get user: " + id, throwable);
return null; // or handle gracefully
});
}
Resource Management
public class UserService {
// ✅ Singleton pattern for client reuse
private static final ApiClient client = new ApiClient.Builder()
.enableRetry()
.enableLogging()
.build();
// ✅ Don't create new clients for each request
public User getUser(String id) {
return client.get("/api/users/" + id)
.execute()
.to(User.class);
}
}
✅ Monitoring Checklist
Health Checks
@RestController
public class HealthController {
private final ApiClient client;
@GetMapping("/health/api")
public ResponseEntity<Map<String, Object>> checkApiHealth() {
try {
// ✅ Check external API health
ApiResponse response = client.get("/api/health")
.timeout(Duration.ofSeconds(5))
.execute();
Map<String, Object> health = Map.of(
"status", response.isSuccess() ? "UP" : "DOWN",
"responseTime", response.getDuration().toMillis(),
"statusCode", response.code()
);
return ResponseEntity.ok(health);
} catch (Exception e) {
Map<String, Object> health = Map.of(
"status", "DOWN",
"error", e.getMessage()
);
return ResponseEntity.status(503).body(health);
}
}
}
Metrics Collection
// ✅ Collect metrics for monitoring
public class MetricsInterceptor implements ResponseInterceptor {
private final MeterRegistry meterRegistry;
@Override
public ApiResponse intercept(ApiResponse response) {
// ✅ Record response metrics
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(Timer.builder("api.requests")
.tag("status", String.valueOf(response.code()))
.register(meterRegistry));
return response;
}
}
✅ Testing Checklist
Unit Tests
@Test
public void testGetUser_Success() {
// ✅ Test successful scenarios
User user = userService.getUser("123");
assertThat(user).isNotNull();
assertThat(user.getId()).isEqualTo("123");
}
@Test
public void testGetUser_NotFound() {
// ✅ Test error scenarios
assertThatThrownBy(() -> userService.getUser("nonexistent"))
.isInstanceOf(UserNotFoundException.class)
.hasMessageContaining("User not found");
}
Integration Tests
@Test
public void testApiIntegration() {
// ✅ Test with real API endpoints
ApiResponse response = client.get("https://httpbin.org/get")
.execute();
assertThat(response.isSuccess()).isTrue();
assertThat(response.code()).isEqualTo(200);
}
✅ Deployment Checklist
Environment Configuration
@Configuration
public class ApiClientConfig {
@Bean
public ApiClient apiClient(@Value("${api.base-url}") String baseUrl,
@Value("${api.timeout:30}") int timeoutSeconds) {
return new ApiClient.Builder()
.baseUrl(baseUrl)
.connectTimeout(Duration.ofSeconds(timeoutSeconds))
.readTimeout(Duration.ofSeconds(timeoutSeconds))
.enableConnectionPooling()
.enableRetryPolicy()
.enableCircuitBreaker()
.enableLogging()
.build();
}
}
Configuration Properties
# application.yml
api:
base-url: https://api.production.com
timeout: 30
logging:
level:
io.mochaapi.client: INFO
management:
endpoints:
web:
exposure:
include: health,metrics,info
✅ Security Hardening
SSL/TLS Configuration
// ✅ Use HTTPS in production
ApiClient client = new ApiClient.Builder()
.baseUrl("https://api.example.com") // ✅ HTTPS only
.build();
// ❌ Never use HTTP in production
// .baseUrl("http://api.example.com")
Secrets Management
// ✅ Use environment variables or secret management
@Value("${api.token}")
private String apiToken;
ApiClient client = new ApiClient.Builder()
.addRequestInterceptor(RequestInterceptor.bearerAuth(() -> apiToken))
.build();
// ❌ Never hardcode secrets
// .addRequestInterceptor(RequestInterceptor.bearerAuth(() -> "hardcoded-token"))
✅ Performance Tuning
Performance Tuning
ApiClient client = new ApiClient.Builder()
// ✅ Optimize for your workload
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.enableRetry(3, Duration.ofSeconds(1))
.enableLogging()
.build();
Note: Connection pooling is handled automatically by Java HttpClient - no manual configuration needed.
✅ Final Verification
Before deploying to production:
- Timeouts configured - Appropriate for your network conditions
- Error handling - Comprehensive exception handling implemented
- Logging enabled - Structured logging with appropriate levels
- Security hardened - HTTPS, input validation, secure authentication
- Performance optimized - Simple retry, async operations, resource management
- Monitoring setup - Health checks and metrics collection
- Tests passing - Unit and integration tests cover critical paths
- Documentation updated - API documentation and runbooks current
Next Steps
- Common Mistakes - What to avoid in production
- Performance Tips - Optimize your HTTP client performance