Real World Example

Handling Payment Failures

A payment service must react differently to different failures: a declined card is shown to the user, a timeout is retried, and an unknown error is logged and reported. Custom exceptions make each case explicit.

Example

import java.util.List;

public class Main {
    public static void main(String[] args) {
        PaymentService service = new PaymentService();
        for (String card : List.of("4111-OK", "4000-DECLINED", "5000-TIMEOUT")) {
            System.out.println(service.pay(card, 999));
        }
    }
}

class PaymentException extends RuntimeException {
    PaymentException(String msg) { super(msg); }
}

class CardDeclinedException extends PaymentException {
    CardDeclinedException(String msg) { super(msg); }
}

class GatewayTimeoutException extends PaymentException {
    GatewayTimeoutException(String msg) { super(msg); }
}

class Gateway {
    private int timeoutCalls = 0;

    String charge(String card, int amount) {
        if (card.endsWith("DECLINED")) throw new CardDeclinedException("Card declined by bank");
        if (card.endsWith("TIMEOUT") && timeoutCalls++ < 1) throw new GatewayTimeoutException("Gateway timed out");
        return "TXN-" + card.substring(0, 4) + "-" + amount;
    }
}

class PaymentService {
    private final Gateway gateway = new Gateway();

    String pay(String card, int amount) {
        int attempts = 0;
        while (true) {
            attempts++;
            try {
                return "SUCCESS " + gateway.charge(card, amount) + " (attempts: " + attempts + ")";
            } catch (CardDeclinedException e) {
                return "FAILED: " + e.getMessage() + ". Please try another card.";
            } catch (GatewayTimeoutException e) {
                System.out.println("  retrying after: " + e.getMessage());
                if (attempts == 3) return "FAILED: gateway unavailable";
            } finally {
                System.out.println("  audit log: attempt " + attempts + " for " + card);
            }
        }
    }
}

Output

  audit log: attempt 1 for 4111-OK
SUCCESS TXN-4111-999 (attempts: 1)
  audit log: attempt 1 for 4000-DECLINED
FAILED: Card declined by bank. Please try another card.
  retrying after: Gateway timed out
  audit log: attempt 1 for 5000-TIMEOUT
  audit log: attempt 2 for 5000-TIMEOUT
SUCCESS TXN-5000-999 (attempts: 2)

Practice

Exercise: Write a method int safeDivide(String a, String b) that parses both strings and divides them. Return -1 and print a clear message for bad numbers and for division by zero.

Show answer
static int safeDivide(String a, String b) {
    try {
        return Integer.parseInt(a) / Integer.parseInt(b);
    } catch (NumberFormatException e) {
        System.out.println("Not a number: " + e.getMessage());
    } catch (ArithmeticException e) {
        System.out.println("Cannot divide by zero");
    }
    return -1;
}