Java: Java | Exception Handling - Failure Boundaries
Let us study Java | Exception Handling like we would in a careful Java review. We will read a small executable example, predict its output, then change one boundary only after we can explain the original contract.
Failure Boundaries is not a checklist to memorize. It is the part of Java design where clear names, deliberate ownership, and observable failures save us from debugging a vague symptom later.
What We Will Practise Together
We are practising more than syntax. For every example, state what Java guarantees, what the caller can observe, and what should happen when the input is missing, invalid, or late.
- Keep the contract smaller than the explanation around it.
- Use the type system and resource boundaries to make invalid states difficult.
- Test the normal path and the closest failure path together.
- Do not log secrets or pretend a failure is a success.
Example 1: Catch the smallest useful exception
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
public static void main(String[] args) {
try {
int port = Integer.parseInt("not-a-number");
System.out.println(port);
} catch (NumberFormatException error) {
System.out.println("invalid port");
}
}
}Expected Result
invalid port
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 2: Reject an invalid argument early
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
static int divide(int total, int parts) {
if (parts == 0) throw new IllegalArgumentException("parts must be positive");
return total / parts;
}
public static void main(String[] args) {
System.out.println(divide(12, 3));
}
}Expected Result
4
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 3: Preserve the cause when rethrowing
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
static int readPort(String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException cause) {
throw new IllegalStateException("Configuration port is invalid", cause);
}
}
public static void main(String[] args) {
try { readPort("x"); } catch (IllegalStateException error) { System.out.println(error.getMessage()); }
}
}Expected Result
Configuration port is invalid
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 4: Use try-with-resources for ownership
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
import java.io.StringReader;
public class Main {
public static void main(String[] args) throws Exception {
try (StringReader reader = new StringReader("42")) {
System.out.println((char) reader.read());
}
}
}Expected Result
4
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 5: Create a domain exception
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
class InsufficientBalanceException extends RuntimeException {
InsufficientBalanceException(String message) { super(message); }
}
public class Main {
static void withdraw(int balance, int amount) {
if (amount > balance) throw new InsufficientBalanceException("balance is too low");
}
public static void main(String[] args) {
try { withdraw(50, 70); } catch (InsufficientBalanceException error) { System.out.println(error.getMessage()); }
}
}Expected Result
balance is too low
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 6: Keep cleanup in finally only when needed
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
public static void main(String[] args) {
try {
System.out.println("work");
} finally {
System.out.println("cleanup");
}
}
}Expected Result
work cleanup
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 7: Return a useful error boundary
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
static String findUser(String id) {
if (id.isBlank()) return "validation: id is required";
return "user=" + id;
}
public static void main(String[] args) {
System.out.println(findUser(""));
}
}Expected Result
validation: id is required
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 8: Do not swallow a failure
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
static void save() { throw new IllegalStateException("database unavailable"); }
public static void main(String[] args) {
try { save(); } catch (IllegalStateException error) { System.out.println("save failed: " + error.getMessage()); }
}
}Expected Result
save failed: database unavailable
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 9: Test the failure contract
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
static int requirePositive(int value) {
if (value <= 0) throw new IllegalArgumentException("positive value required");
return value;
}
public static void main(String[] args) {
try { requirePositive(0); } catch (IllegalArgumentException error) { System.out.println(error.getMessage()); }
}
}Expected Result
positive value required
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Example 10: Review what the caller can know
Let us trace this Java decision together. First say what the input represents. Then identify the exact rule Java applies. Finally compare the expected output with what you predicted before editing the snippet.
A strong implementation keeps the nearest failure, type boundary, or resource owner visible. That makes this example safe to reuse instead of merely impressive in isolation.
Java
public class Main {
static String open(boolean available) {
if (!available) throw new IllegalStateException("service unavailable");
return "opened";
}
public static void main(String[] args) {
try { System.out.println(open(false)); } catch (IllegalStateException error) { System.out.println("retry later"); }
}
}Expected Result
retry later
Teacher check: alter one input or boundary, predict the new output, and explain the behavior in Java terms before you move on.
Teacher Review
Before we close failure boundaries, narrate one example without looking at the code. If you can state the input, rule, output, and boundary in order, you have a reusable Java decision rather than an isolated answer.
Now carry one example into a small project. Keep its expected result beside the code while you change it. That is how we build reliable Java judgment one visible contract at a time.
Practice Before You Move On
Choose one snippet and make a small deliberate change. Write the predicted result first, then run it. If the result surprises you, reduce the example until every step is explainable.
