Exceptions
Throw and Throws
Signaling Errors
Your validation method checks if age is negative. You need to signal "this is
wrong" to the caller. throw creates and throws an exception. throws in the
signature warns callers what might go wrong.
Throw an exception
Manually trigger an exception.
// The throw Keyword
public class ThrowKeyword {
public static void main(String[] args) {
System.out.println("=== The throw Keyword ===\n");
// Basic throw
System.out.println("--- Basic throw ---");
int testAge = ;
try {
validateAge(testAge);
} catch (IllegalArgumentException e) {
System.out.println("Caught: " + e.getMessage());
}
try {
validateAge(25);
System.out.println("Age 25 is valid!");
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
// Throw different exception types
System.out.println("\n--- Different Exception Types ---");
testExceptionTypes();
// Throw with condition
System.out.println("\n--- Conditional throw ---");
String[] names = {"Alice", "", "Bob", null};
for (String name : names) {
try {
validateName(name);
System.out.println("'" + name + "' is valid");
} catch (IllegalArgumentException e) {
System.out.println("Invalid name: " + e.getMessage());
}
}
// Throw in constructor
System.out.println("\n--- Throw in Constructor ---");
try {
Person p1 = new Person("John", 30);
System.out.println("Created: " + p1);
Person p2 = new Person("", 25);
System.out.println("Created: " + p2);
} catch (IllegalArgumentException e) {
System.out.println("Cannot create Person: " + e.getMessage());
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. throw new ExceptionType("message")
2. throw immediately exits the method
3. Can throw from anywhere: methods, constructors, blocks
4. Must throw a Throwable (usually Exception subclass)
5. Code after throw in same block won't execute
""");
}
static void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
if (age > 150) {
throw new IllegalArgumentException("Age unrealistic: " + age);
}
// If we reach here, age is valid
System.out.println("Validating age: " + age + " - OK");
}
static void validateName(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
if (name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
}
static void testExceptionTypes() {
// Different exception types
String[] tests = {"null_pointer", "illegal_arg", "illegal_state", "arithmetic"};
for (String test : tests) {
try {
throwSpecificType(test);
} catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
static void throwSpecificType(String type) {
switch (type) {
case "null_pointer" -> throw new NullPointerException("Something was null");
case "illegal_arg" -> throw new IllegalArgumentException("Bad argument");
case "illegal_state" -> throw new IllegalStateException("Wrong state");
case "arithmetic" -> throw new ArithmeticException("Math error");
}
}
}
class Person {
String name;
int age;
Person(String name, int age) {
// Validate in constructor
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name is required");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throw Keyword
public class ThrowKeyword {
public static void main(String[] args) {
System.out.println("=== The throw Keyword ===\n");
// Basic throw
System.out.println("--- Basic throw ---");
int testAge = ;
try {
validateAge(testAge);
} catch (IllegalArgumentException e) {
System.out.println("Caught: " + e.getMessage());
}
try {
validateAge(25);
System.out.println("Age 25 is valid!");
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
// Throw different exception types
System.out.println("\n--- Different Exception Types ---");
testExceptionTypes();
// Throw with condition
System.out.println("\n--- Conditional throw ---");
String[] names = {"Alice", "", "Bob", null};
for (String name : names) {
try {
validateName(name);
System.out.println("'" + name + "' is valid");
} catch (IllegalArgumentException e) {
System.out.println("Invalid name: " + e.getMessage());
}
}
// Throw in constructor
System.out.println("\n--- Throw in Constructor ---");
try {
Person p1 = new Person("John", 30);
System.out.println("Created: " + p1);
Person p2 = new Person("", 25);
System.out.println("Created: " + p2);
} catch (IllegalArgumentException e) {
System.out.println("Cannot create Person: " + e.getMessage());
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. throw new ExceptionType("message")
2. throw immediately exits the method
3. Can throw from anywhere: methods, constructors, blocks
4. Must throw a Throwable (usually Exception subclass)
5. Code after throw in same block won't execute
""");
}
static void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
if (age > 150) {
throw new IllegalArgumentException("Age unrealistic: " + age);
}
// If we reach here, age is valid
System.out.println("Validating age: " + age + " - OK");
}
static void validateName(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
if (name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
}
static void testExceptionTypes() {
// Different exception types
String[] tests = {"null_pointer", "illegal_arg", "illegal_state", "arithmetic"};
for (String test : tests) {
try {
throwSpecificType(test);
} catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
static void throwSpecificType(String type) {
switch (type) {
case "null_pointer" -> throw new NullPointerException("Something was null");
case "illegal_arg" -> throw new IllegalArgumentException("Bad argument");
case "illegal_state" -> throw new IllegalStateException("Wrong state");
case "arithmetic" -> throw new ArithmeticException("Math error");
}
}
}
class Person {
String name;
int age;
Person(String name, int age) {
// Validate in constructor
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name is required");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throw Keyword
public class ThrowKeyword {
public static void main(String[] args) {
System.out.println("=== The throw Keyword ===\n");
// Basic throw
System.out.println("--- Basic throw ---");
int testAge = ;
try {
validateAge(testAge);
} catch (IllegalArgumentException e) {
System.out.println("Caught: " + e.getMessage());
}
try {
validateAge(25);
System.out.println("Age 25 is valid!");
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
// Throw different exception types
System.out.println("\n--- Different Exception Types ---");
testExceptionTypes();
// Throw with condition
System.out.println("\n--- Conditional throw ---");
String[] names = {"Alice", "", "Bob", null};
for (String name : names) {
try {
validateName(name);
System.out.println("'" + name + "' is valid");
} catch (IllegalArgumentException e) {
System.out.println("Invalid name: " + e.getMessage());
}
}
// Throw in constructor
System.out.println("\n--- Throw in Constructor ---");
try {
Person p1 = new Person("John", 30);
System.out.println("Created: " + p1);
Person p2 = new Person("", 25);
System.out.println("Created: " + p2);
} catch (IllegalArgumentException e) {
System.out.println("Cannot create Person: " + e.getMessage());
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. throw new ExceptionType("message")
2. throw immediately exits the method
3. Can throw from anywhere: methods, constructors, blocks
4. Must throw a Throwable (usually Exception subclass)
5. Code after throw in same block won't execute
""");
}
static void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
if (age > 150) {
throw new IllegalArgumentException("Age unrealistic: " + age);
}
// If we reach here, age is valid
System.out.println("Validating age: " + age + " - OK");
}
static void validateName(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
if (name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
}
static void testExceptionTypes() {
// Different exception types
String[] tests = {"null_pointer", "illegal_arg", "illegal_state", "arithmetic"};
for (String test : tests) {
try {
throwSpecificType(test);
} catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
static void throwSpecificType(String type) {
switch (type) {
case "null_pointer" -> throw new NullPointerException("Something was null");
case "illegal_arg" -> throw new IllegalArgumentException("Bad argument");
case "illegal_state" -> throw new IllegalStateException("Wrong state");
case "arithmetic" -> throw new ArithmeticException("Math error");
}
}
}
class Person {
String name;
int age;
Person(String name, int age) {
// Validate in constructor
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name is required");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
throw new Exception("message") creates and throws immediately.
Declare with throws
Tell callers what exceptions a method might throw.
// The throws Declaration
import java.io.IOException;
import java.text.ParseException;
public class ThrowsDeclaration {
public static void main(String[] args) {
System.out.println("=== The throws Declaration ===\n");
// Method that throws unchecked exception
System.out.println("--- Unchecked Exceptions (no throws needed) ---");
try {
divideNumbers(10, 0);
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
}
// Method that throws checked exception
System.out.println("\n--- Checked Exceptions (throws required) ---");
try {
processFile("data.txt");
} catch (IOException e) {
System.out.println("Caught IOException: " + e.getMessage());
}
// Multiple throws declaration
System.out.println("\n--- Multiple Exceptions in throws ---");
String data = ;
try {
parseAndProcess(data);
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
} catch (ParseException e) {
System.out.println("Parse Error: " + e.getMessage());
}
// Throws vs handling
System.out.println("\n--- Throws vs Handling ---");
demonstrateChoice();
// Throws in main method
System.out.println("\n=== throws in main() ===");
System.out.println("""
// main can also declare throws:
public static void main(String[] args) throws IOException {
// If IOException occurs, program crashes
// No try-catch needed
}
Note: Usually better to catch in main rather than throws.
""");
System.out.println("=== Key Points ===");
System.out.println("""
1. throws goes in method signature after parameters
2. Required for checked exceptions (not RuntimeException)
3. Multiple: throws IOException, ParseException
4. Caller must catch OR also declare throws
5. Unchecked exceptions don't require throws
""");
}
// Unchecked - no throws needed
static int divideNumbers(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
// Checked - throws required
static void processFile(String filename) throws IOException {
System.out.println("Attempting to process: " + filename);
// Simulating file operation that might fail
if (!filename.endsWith(".txt")) {
throw new IOException("Only .txt files supported");
}
// Simulate file not found
if (filename.equals("data.txt")) {
throw new IOException("File not found: " + filename);
}
System.out.println("File processed successfully");
}
// Multiple exceptions in throws
static void parseAndProcess(String data) throws IOException, ParseException {
System.out.println("Processing data: " + data);
if (data == null || data.isEmpty()) {
throw new IOException("No data provided");
}
if (!data.matches("\\d+")) {
throw new ParseException("Data is not numeric: " + data, 0);
}
System.out.println("Data valid: " + data);
}
static void demonstrateChoice() {
System.out.println("Option 1: Catch the exception");
System.out.println(" try { processFile(); } catch (IOException e) { ... }");
System.out.println();
System.out.println("Option 2: Propagate with throws");
System.out.println(" void myMethod() throws IOException { processFile(); }");
System.out.println();
System.out.println("Choose based on whether YOU can handle the error,");
System.out.println("or if the CALLER should handle it.");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throws Declaration
import java.io.IOException;
import java.text.ParseException;
public class ThrowsDeclaration {
public static void main(String[] args) {
System.out.println("=== The throws Declaration ===\n");
// Method that throws unchecked exception
System.out.println("--- Unchecked Exceptions (no throws needed) ---");
try {
divideNumbers(10, 0);
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
}
// Method that throws checked exception
System.out.println("\n--- Checked Exceptions (throws required) ---");
try {
processFile("data.txt");
} catch (IOException e) {
System.out.println("Caught IOException: " + e.getMessage());
}
// Multiple throws declaration
System.out.println("\n--- Multiple Exceptions in throws ---");
String data = ;
try {
parseAndProcess(data);
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
} catch (ParseException e) {
System.out.println("Parse Error: " + e.getMessage());
}
// Throws vs handling
System.out.println("\n--- Throws vs Handling ---");
demonstrateChoice();
// Throws in main method
System.out.println("\n=== throws in main() ===");
System.out.println("""
// main can also declare throws:
public static void main(String[] args) throws IOException {
// If IOException occurs, program crashes
// No try-catch needed
}
Note: Usually better to catch in main rather than throws.
""");
System.out.println("=== Key Points ===");
System.out.println("""
1. throws goes in method signature after parameters
2. Required for checked exceptions (not RuntimeException)
3. Multiple: throws IOException, ParseException
4. Caller must catch OR also declare throws
5. Unchecked exceptions don't require throws
""");
}
// Unchecked - no throws needed
static int divideNumbers(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
// Checked - throws required
static void processFile(String filename) throws IOException {
System.out.println("Attempting to process: " + filename);
// Simulating file operation that might fail
if (!filename.endsWith(".txt")) {
throw new IOException("Only .txt files supported");
}
// Simulate file not found
if (filename.equals("data.txt")) {
throw new IOException("File not found: " + filename);
}
System.out.println("File processed successfully");
}
// Multiple exceptions in throws
static void parseAndProcess(String data) throws IOException, ParseException {
System.out.println("Processing data: " + data);
if (data == null || data.isEmpty()) {
throw new IOException("No data provided");
}
if (!data.matches("\\d+")) {
throw new ParseException("Data is not numeric: " + data, 0);
}
System.out.println("Data valid: " + data);
}
static void demonstrateChoice() {
System.out.println("Option 1: Catch the exception");
System.out.println(" try { processFile(); } catch (IOException e) { ... }");
System.out.println();
System.out.println("Option 2: Propagate with throws");
System.out.println(" void myMethod() throws IOException { processFile(); }");
System.out.println();
System.out.println("Choose based on whether YOU can handle the error,");
System.out.println("or if the CALLER should handle it.");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throws Declaration
import java.io.IOException;
import java.text.ParseException;
public class ThrowsDeclaration {
public static void main(String[] args) {
System.out.println("=== The throws Declaration ===\n");
// Method that throws unchecked exception
System.out.println("--- Unchecked Exceptions (no throws needed) ---");
try {
divideNumbers(10, 0);
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
}
// Method that throws checked exception
System.out.println("\n--- Checked Exceptions (throws required) ---");
try {
processFile("data.txt");
} catch (IOException e) {
System.out.println("Caught IOException: " + e.getMessage());
}
// Multiple throws declaration
System.out.println("\n--- Multiple Exceptions in throws ---");
String data = ;
try {
parseAndProcess(data);
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
} catch (ParseException e) {
System.out.println("Parse Error: " + e.getMessage());
}
// Throws vs handling
System.out.println("\n--- Throws vs Handling ---");
demonstrateChoice();
// Throws in main method
System.out.println("\n=== throws in main() ===");
System.out.println("""
// main can also declare throws:
public static void main(String[] args) throws IOException {
// If IOException occurs, program crashes
// No try-catch needed
}
Note: Usually better to catch in main rather than throws.
""");
System.out.println("=== Key Points ===");
System.out.println("""
1. throws goes in method signature after parameters
2. Required for checked exceptions (not RuntimeException)
3. Multiple: throws IOException, ParseException
4. Caller must catch OR also declare throws
5. Unchecked exceptions don't require throws
""");
}
// Unchecked - no throws needed
static int divideNumbers(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
// Checked - throws required
static void processFile(String filename) throws IOException {
System.out.println("Attempting to process: " + filename);
// Simulating file operation that might fail
if (!filename.endsWith(".txt")) {
throw new IOException("Only .txt files supported");
}
// Simulate file not found
if (filename.equals("data.txt")) {
throw new IOException("File not found: " + filename);
}
System.out.println("File processed successfully");
}
// Multiple exceptions in throws
static void parseAndProcess(String data) throws IOException, ParseException {
System.out.println("Processing data: " + data);
if (data == null || data.isEmpty()) {
throw new IOException("No data provided");
}
if (!data.matches("\\d+")) {
throw new ParseException("Data is not numeric: " + data, 0);
}
System.out.println("Data valid: " + data);
}
static void demonstrateChoice() {
System.out.println("Option 1: Catch the exception");
System.out.println(" try { processFile(); } catch (IOException e) { ... }");
System.out.println();
System.out.println("Option 2: Propagate with throws");
System.out.println(" void myMethod() throws IOException { processFile(); }");
System.out.println();
System.out.println("Choose based on whether YOU can handle the error,");
System.out.println("or if the CALLER should handle it.");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
void method() throws IOException - caller must handle or declare.
Exception propagation
Exceptions bubble up until caught.
// Exception Propagation
public class Propagation {
public static void main(String[] args) {
System.out.println("=== Exception Propagation ===\n");
// Show the call stack
System.out.println("Call stack: main() → level1() → level2() → level3()");
System.out.println();
// Exception bubbles up
System.out.println("--- Exception Bubbles Up ---");
try {
System.out.println("main: Calling level1()...");
level1();
System.out.println("main: level1() returned");
} catch (RuntimeException e) {
System.out.println("main: Caught exception!");
System.out.println("main: Message = " + e.getMessage());
}
System.out.println("main: Continuing after catch...\n");
// Where to catch?
System.out.println("--- Where Should You Catch? ---");
demonstrateCatchLevels();
// Propagation visualization
System.out.println("\n--- Propagation Visualization ---");
System.out.println("""
Call stack (going down):
┌─────────────────────┐
│ main() │ ← catches here
│ ↓ │
│ level1() │ ← no catch, propagates up
│ ↓ │
│ level2() │ ← no catch, propagates up
│ ↓ │
│ level3() │ ← THROWS exception
└─────────────────────┘
Exception propagates (going up):
level3() → level2() → level1() → main() [CAUGHT]
""");
System.out.println("=== Key Points ===");
System.out.println("""
1. Exception "bubbles up" the call stack
2. Each method can catch OR let it propagate
3. Catch where you can meaningfully handle
4. If never caught, program crashes
5. Stack trace shows propagation path
""");
}
static void level1() {
System.out.println(" level1: Calling level2()...");
level2();
System.out.println(" level1: level2() returned");
}
static void level2() {
System.out.println(" level2: Calling level3()...");
level3();
System.out.println(" level2: level3() returned");
}
static void level3() {
System.out.println(" level3: About to throw...");
throw new RuntimeException("Error in level3!");
// Code below never executes
}
static void demonstrateCatchLevels() {
System.out.println("\nOption 1: Catch at the source (level3)");
catchAtLevel3();
System.out.println("\nOption 2: Catch in the middle (level2)");
catchAtLevel2();
System.out.println("\nOption 3: Catch at the top (level1)");
catchAtLevel1();
}
static void catchAtLevel3() {
System.out.println(" Handling error immediately where it occurs");
try {
throw new RuntimeException("Error!");
} catch (RuntimeException e) {
System.out.println(" Caught in same method: " + e.getMessage());
}
}
static void catchAtLevel2() {
try {
doRiskyOperation();
} catch (RuntimeException e) {
System.out.println(" Caught one level up: " + e.getMessage());
}
}
static void doRiskyOperation() {
throw new RuntimeException("Error in risky operation!");
}
static void catchAtLevel1() {
try {
intermediate();
} catch (RuntimeException e) {
System.out.println(" Caught two levels up: " + e.getMessage());
}
}
static void intermediate() {
deepMethod();
}
static void deepMethod() {
throw new RuntimeException("Error from deep!");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Uncaught exception travels up the call stack to the first handler.
Checked vs unchecked
Some exceptions must be handled, others don't.
// Checked vs Unchecked Exceptions
import java.io.IOException;
public class CheckedVsUnchecked {
public static void main(String[] args) {
System.out.println("=== Checked vs Unchecked Exceptions ===\n");
// Exception hierarchy
System.out.println("--- Exception Hierarchy ---");
System.out.println("""
Throwable
├── Error (don't catch - system errors)
│ └── OutOfMemoryError, StackOverflowError
└── Exception
├── RuntimeException (UNCHECKED)
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ ├── ArithmeticException
│ └── IndexOutOfBoundsException
└── Other Exceptions (CHECKED)
├── IOException
├── SQLException
└── ParseException
""");
// Unchecked exceptions
System.out.println("--- Unchecked Exceptions (RuntimeException) ---");
System.out.println("• No 'throws' required");
System.out.println("• try-catch is optional");
System.out.println("• Usually programming errors\n");
testUnchecked();
// Checked exceptions
System.out.println("\n--- Checked Exceptions ---");
System.out.println("• 'throws' declaration required");
System.out.println("• Caller MUST catch or declare throws");
System.out.println("• Usually external failures (IO, network)\n");
testChecked();
// Comparison
System.out.println("\n--- Side-by-Side Comparison ---");
showComparison();
System.out.println("\n=== When to Use Which? ===");
System.out.println("""
UNCHECKED (RuntimeException):
• Programming errors (bugs)
• Invalid arguments
• Null pointer issues
• Logic errors
CHECKED (Exception):
• External failures
• File not found
• Network errors
• Database connection issues
• Things caller should be prepared to handle
""");
}
// No throws needed for unchecked
static void testUnchecked() {
System.out.println("Method signature: static void testUnchecked()");
System.out.println("No 'throws' needed!\n");
// Method that throws unchecked - no declaration
System.out.println("Calling methods that throw unchecked exceptions:");
try {
throwNullPointer();
} catch (NullPointerException e) {
System.out.println(" Caught NullPointerException: " + e.getMessage());
}
try {
throwIllegalArg();
} catch (IllegalArgumentException e) {
System.out.println(" Caught IllegalArgumentException: " + e.getMessage());
}
}
// No throws in signature
static void throwNullPointer() {
throw new NullPointerException("Value was null");
}
static void throwIllegalArg() {
throw new IllegalArgumentException("Bad argument");
}
// Throws required for checked
static void testChecked() {
System.out.println("Caller must use try-catch OR declare throws\n");
// Option 1: Use try-catch
System.out.println("Option 1: try-catch (handle here)");
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println(" Caught IOException: " + e.getMessage());
}
// Option 2 would be: declare throws in method signature
System.out.println("\nOption 2: Declare 'throws' (propagate to caller)");
System.out.println(" void myMethod() throws IOException { readFile(); }");
}
// throws IOException is REQUIRED
static void readFile(String filename) throws IOException {
throw new IOException("Cannot read file: " + filename);
}
static void showComparison() {
System.out.println("""
┌────────────────────────────────────────────────────────────┐
│ Feature │ Unchecked │ Checked │
├────────────────────────────────────────────────────────────┤
│ Base class │ RuntimeException │ Exception │
│ 'throws' required │ No │ Yes │
│ Catch required │ No │ Yes │
│ Compiler checks │ No │ Yes │
│ Typical cause │ Programming bug │ External failure │
│ Example │ NullPointerEx │ IOException │
└────────────────────────────────────────────────────────────┘
""");
// Code comparison
System.out.println("Code Comparison:");
System.out.println();
System.out.println("// UNCHECKED - compiles fine without try-catch:");
System.out.println("void demo1() {");
System.out.println(" throw new IllegalArgumentException(\"error\");");
System.out.println("}");
System.out.println();
System.out.println("// CHECKED - compiler error without throws/try-catch:");
System.out.println("void demo2() throws IOException { // Must declare!");
System.out.println(" throw new IOException(\"error\");");
System.out.println("}");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Checked: must handle or declare (IOException). Unchecked: optional (NullPointerException).
Catch, log, rethrow
Handle partially then pass along.
// Catching, Processing, and Rethrowing
public class Rethrow {
public static void main(String[] args) {
System.out.println("=== Catching and Rethrowing ===\n");
// Simple rethrow
System.out.println("--- Simple Rethrow ---");
try {
simpleRethrow();
} catch (RuntimeException e) {
System.out.println("Final catch: " + e.getMessage());
}
// Rethrow with additional context
System.out.println("\n--- Rethrow with Additional Context ---");
try {
rethrowWithContext("user123");
} catch (RuntimeException e) {
System.out.println("Final catch: " + e.getMessage());
System.out.println("Cause: " + e.getCause());
}
// Rethrow as different type
System.out.println("\n--- Rethrow as Different Type ---");
try {
wrapAndRethrow();
} catch (RuntimeException e) {
System.out.println("Exception type: " + e.getClass().getSimpleName());
System.out.println("Message: " + e.getMessage());
if (e.getCause() != null) {
System.out.println("Original cause: " + e.getCause().getClass().getSimpleName());
}
}
// Log and rethrow
System.out.println("\n--- Log and Rethrow ---");
try {
logAndRethrow();
} catch (RuntimeException e) {
System.out.println("Handled at top level");
}
// Conditional rethrow
System.out.println("\n--- Conditional Rethrow ---");
conditionalRethrowDemo();
System.out.println("\n=== Common Patterns ===");
System.out.println("""
1. Log and rethrow (don't swallow)
2. Wrap in higher-level exception
3. Add context information
4. Convert checked to unchecked
5. Conditional handling
""");
}
static void simpleRethrow() {
try {
throw new RuntimeException("Original error");
} catch (RuntimeException e) {
System.out.println("Caught, now rethrowing...");
throw e;
}
}
static void rethrowWithContext(String userId) {
try {
processUser(userId);
} catch (RuntimeException e) {
// Add context and rethrow
throw new RuntimeException(
"Failed to process user: " + userId,
e // Original exception as cause
);
}
}
static void processUser(String userId) {
throw new RuntimeException("Database connection failed");
}
static void wrapAndRethrow() {
try {
riskyOperation();
} catch (IllegalArgumentException e) {
// Wrap specific exception in more general one
throw new RuntimeException("Operation failed", e);
}
}
static void riskyOperation() {
throw new IllegalArgumentException("Invalid input");
}
static void logAndRethrow() {
try {
performTask();
} catch (RuntimeException e) {
// Log the error
System.out.println(" [LOG] Error occurred: " + e.getMessage());
System.out.println(" [LOG] Stack trace logged");
// Rethrow for higher-level handling
throw e;
}
}
static void performTask() {
throw new RuntimeException("Task execution error");
}
static void conditionalRethrowDemo() {
int[] errorCodes = {1, 2, 3};
for (int code : errorCodes) {
try {
throwWithCode(code);
} catch (RuntimeException e) {
if (code == 2) {
// Recoverable - handle and continue
System.out.println("Code " + code + ": Handled (recoverable)");
} else {
// Not recoverable - report but continue demo
System.out.println("Code " + code + ": " + e.getMessage() + " (would rethrow)");
// In real code: throw e;
}
}
}
}
static void throwWithCode(int code) {
throw new RuntimeException("Error code " + code);
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Catch, do something (log), then throw e to propagate.
Exercise: Practical.java
Build a validation system with proper exception throwing