Skip to content

Fail Fast

Definition

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 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

Fail Fast Tips