Fail Fast
Definition

Examples
// BAD: Fail slow - problem propagates
public class UserService {
public void updateUser(String userId, String email) {
// No validation - accepts anything
User user = userRepo.findById(userId); // Might return null
user.setEmail(email); // NPE if user is null
// Silent corruption if email invalid
userRepo.save(user); // Saves bad data
// Problem discovered later when email fails to send
}
}
// GOOD: Fail fast - immediate validation
public class UserService {
public void updateUser(String userId, String email) {
// Validate early
Objects.requireNonNull(userId, "userId cannot be null");
Objects.requireNonNull(email, "email cannot be null");
if (!EmailValidator.isValid(email)) {
throw new IllegalArgumentException(
"Invalid email format: " + email);
}
User user = userRepo.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
user.setEmail(email);
userRepo.save(user);
}
}
Constructor Validation
// GOOD: Fail fast in constructors
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
// Fail fast - invalid Money cannot exist
if (amount == null) {
throw new IllegalArgumentException("amount cannot be null");
}
if (currency == null) {
throw new IllegalArgumentException("currency cannot be null");
}
if (amount.scale() > currency.getDefaultFractionDigits()) {
throw new IllegalArgumentException(
"amount scale exceeds currency precision");
}
this.amount = amount;
this.currency = currency;
}
}
public class DateRange {
private final LocalDate start;
private final LocalDate end;
public DateRange(LocalDate start, LocalDate end) {
Objects.requireNonNull(start, "start cannot be null");
Objects.requireNonNull(end, "end cannot be null");
if (end.isBefore(start)) {
throw new IllegalArgumentException(
"end date cannot be before start date: " +
start + " - " + end);
}
this.start = start;
this.end = end;
}
}
// BAD: Allows invalid state
public class DateRange {
private LocalDate start;
private LocalDate end;
public DateRange() { } // Empty constructor allows invalid state
public void setStart(LocalDate start) { this.start = start; }
public void setEnd(LocalDate end) { this.end = end; }
// Object can exist with null dates or end < start
}
API Validation
// Fail fast at API boundaries
@RestController
public class OrderController {
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(
@Valid @RequestBody OrderRequest request) { // @Valid triggers validation
return ResponseEntity.ok(orderService.create(request));
}
}
// Request validation with annotations
public class OrderRequest {
@NotNull(message = "customerId is required")
private String customerId;
@NotEmpty(message = "items cannot be empty")
@Size(max = 100, message = "maximum 100 items per order")
private List<@Valid OrderItem> items;
@NotNull(message = "shipping address is required")
@Valid
private Address shippingAddress;
}
public class OrderItem {
@NotNull(message = "productId is required")
private String productId;
@Min(value = 1, message = "quantity must be at least 1")
@Max(value = 999, message = "quantity cannot exceed 999")
private int quantity;
}
// Custom validation with clear error
@Component
public class OrderValidator {
public void validate(OrderRequest request) {
// Business rule validation
if (request.getItems().stream()
.map(OrderItem::getProductId)
.distinct()
.count() != request.getItems().size()) {
throw new ValidationException(
"Order contains duplicate products. " +
"Use quantity instead of duplicate items.");
}
}
}
Fail Fast Patterns

Fail Fast in Collections
// Java Collections fail fast on concurrent modification
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// Fails fast with ConcurrentModificationException
for (String item : list) {
if (item.equals("b")) {
list.remove(item); // Modification during iteration!
}
}
// Correct approach
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("b")) {
iterator.remove(); // Safe removal
}
}
// Or use removeIf (Java 8+)
list.removeIf(item -> item.equals("b"));
// Fail fast iterators in HashMap
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
for (String key : map.keySet()) {
map.put("b", 2); // ConcurrentModificationException!
}
// Why fail fast is better than silent corruption:
// - Immediately shows problem
// - Stack trace points to exact location
// - No data corruption from partial operation
Configuration Fail Fast
// Fail fast during application startup
@Configuration
public class AppConfig {
@Value("${database.url}")
private String databaseUrl;
@Value("${api.key}")
private String apiKey;
@PostConstruct
public void validateConfiguration() {
// Fail fast if configuration is invalid
List<String> errors = new ArrayList<>();
if (databaseUrl == null || databaseUrl.isBlank()) {
errors.add("database.url is required");
}
if (apiKey == null || apiKey.isBlank()) {
errors.add("api.key is required");
} else if (apiKey.length() < 32) {
errors.add("api.key must be at least 32 characters");
}
if (!errors.isEmpty()) {
throw new IllegalStateException(
"Configuration errors:\n" +
String.join("\n", errors)
);
}
}
}
// Fail fast with dependency checks
@Component
public class HealthCheck {
@PostConstruct
public void verifyDependencies() {
// Check database connection
if (!canConnectToDatabase()) {
throw new IllegalStateException(
"Cannot connect to database at startup");
}
// Check external service
if (!isExternalServiceReachable()) {
throw new IllegalStateException(
"External service unreachable at startup");
}
}
}
Tips & Tricks
