Java Tutorial
🔍

What is Exception Handling in Java?

What is Exception Handling in Java?

Exception handling is the mechanism Java uses to deal with runtime errors without crashing the entire program. When something goes wrong — a file is missing, a network call times out, a user passes null where an object is expected — Java creates an exception object that describes what happened and gives the program a structured way to respond. Without exception handling, any of these failures would terminate the JVM immediately with a stack trace and no ability to recover, log, or return a meaningful response to the user.

Every production Java application you work in will have exception handling woven through every layer — from the database access layer to the REST controller. Understanding this mechanism deeply is not optional.

What Is an Exception?

An exception is a Java object — specifically, an instance of Throwable or one of its subclasses — created at the point where something abnormal happens during execution. The JVM stops normal execution, creates this object with information about what went wrong and where, and begins searching for code that knows how to handle it.

The word "exception" is well-chosen. It signals an exceptional condition — something outside the normal expected flow. Division by zero, accessing an array index that does not exist, calling a method on a null reference: these are not bugs in the Java language; they are runtime situations the program must handle. Exception handling gives you the vocabulary to respond.

NORMAL PROGRAM FLOW vs EXCEPTION FLOW:

  Normal:
    main() → methodA() → methodB() → returns result → methodA() continues

  With exception in methodB():
    main() → methodA() → methodB() → EXCEPTION CREATED
                                          |
                              JVM searches for handler:
                              methodB()? No handler → unwind
                              methodA()? No handler → unwind
                              main()?    Has catch   → handle here

Java Exception Hierarchy

Every exception in Java descends from Throwable. Two branches come directly off it: Error and Exception. Understanding this tree is not just theory — it directly determines what you should catch, what you should not catch, and how you design exception strategies.

java.lang.Throwable
    |
    +── java.lang.Error
    |       JVM-level failures — do NOT catch these in application code
    |       ├── OutOfMemoryError
    |       ├── StackOverflowError
    |       └── VirtualMachineError
    |
    +── java.lang.Exception
            All application-level exceptions
            |
            +── java.lang.RuntimeException  ← UNCHECKED
            |       Compiler does not require handling
            |       ├── NullPointerException
            |       ├── IllegalArgumentException
            |       ├── ArrayIndexOutOfBoundsException
            |       ├── ClassCastException
            |       └── NumberFormatException
            |
            +── IOException               ← CHECKED
            |       Must be handled or declared with throws
            |       ├── FileNotFoundException
            |       └── SocketException
            |
            +── SQLException             ← CHECKED
            +── ClassNotFoundException   ← CHECKED
            +── InterruptedException     ← CHECKED

The distinction between checked and unchecked exceptions is the most important classification in this hierarchy, and it is worth understanding clearly before writing any code.

Checked exceptions are those the compiler forces you to handle. If you call a method that declares throws IOException and you do not catch it or propagate it with throws, your code does not compile. These represent failures that are reasonably expected and recoverable — a file might not exist, a network connection might drop.

Unchecked exceptions extend RuntimeException. The compiler does not enforce handling. They typically signal programming errors: null dereference, bad array index, illegal argument. You can catch them, but catching them is not required.

Errors are separate from exceptions entirely. OutOfMemoryError, StackOverflowError — these are JVM-level failures beyond the application's control. Catching them is almost always the wrong move.

Syntax and Usage

Basic try-catch

The try block wraps code that might throw. Each catch block handles a specific exception type. The JVM checks each catch block from top to bottom and executes the first one whose type matches the thrown exception.

Java
1// File: BasicExceptionDemo.java 2 3public class BasicExceptionDemo { 4 5 public static void main(String[] args) { 6 7 // Attempting to parse invalid input — NumberFormatException is unchecked 8 String userInput = "not-a-number"; 9 10 try { 11 int parsed = Integer.parseInt(userInput); 12 System.out.println("Parsed value: " + parsed); 13 } catch (NumberFormatException exception) { 14 // Execution jumps here immediately when parseInt throws 15 System.out.println("Invalid number format: " + exception.getMessage()); 16 } 17 18 System.out.println("Program continues after the catch block"); 19 } 20}
Output:
Invalid number format: For input string: "not-a-number"
Program continues after the catch block

Multiple Catch Blocks and Catch Order

When multiple exceptions are possible, each gets its own catch block. The order matters: more specific exception types must appear before more general ones. A catch (Exception e) before a catch (IOException e) would make the IOException block unreachable — the compiler flags this as an error.

Java
1// File: MultipleCatchDemo.java 2 3import java.io.FileNotFoundException; 4import java.io.IOException; 5 6public class MultipleCatchDemo { 7 8 public static void processFile(String filePath) throws IOException { 9 if (filePath == null) { 10 throw new IllegalArgumentException("File path cannot be null"); 11 } 12 if (filePath.isEmpty()) { 13 throw new FileNotFoundException("No file found at: " + filePath); 14 } 15 // File reading logic would go here 16 System.out.println("Processing: " + filePath); 17 } 18 19 public static void main(String[] args) { 20 21 String[] paths = {null, "", "/valid/path.txt"}; 22 23 for (String path : paths) { 24 try { 25 processFile(path); 26 } catch (IllegalArgumentException exception) { 27 // More specific — handled first 28 System.out.println("Argument error: " + exception.getMessage()); 29 } catch (FileNotFoundException exception) { 30 // More specific than IOException — must come before it 31 System.out.println("File missing: " + exception.getMessage()); 32 } catch (IOException exception) { 33 // Most general checked exception — handled last 34 System.out.println("IO failure: " + exception.getMessage()); 35 } 36 } 37 } 38}
Output:
Argument error: File path cannot be null
File missing: No file found at:
Processing: /valid/path.txt

finally Block

The finally block executes regardless of whether an exception was thrown or caught. It runs after try succeeds, after any matching catch executes, and even when a return statement is inside try. The one exception: System.exit() prevents finally from running.

finally exists for cleanup that must happen no matter what — closing a database connection, releasing a lock, shutting down a thread. In modern Java, try-with-resources handles most of these cases automatically, but finally is still the right tool for non-AutoCloseable cleanup.

Java
1// File: FinallyDemo.java 2 3public class FinallyDemo { 4 5 public static String processOrder(String orderId) { 6 System.out.println("Starting transaction for: " + orderId); 7 try { 8 if (orderId == null) { 9 throw new IllegalArgumentException("Order ID required"); 10 } 11 System.out.println("Order processed: " + orderId); 12 return "SUCCESS"; 13 } catch (IllegalArgumentException exception) { 14 System.out.println("Processing failed: " + exception.getMessage()); 15 return "FAILED"; 16 } finally { 17 // Always runs — connection cleanup, metric recording, audit log 18 System.out.println("Transaction closed — audit log updated"); 19 } 20 } 21 22 public static void main(String[] args) { 23 System.out.println("Result: " + processOrder("ORD-9821")); 24 System.out.println(); 25 System.out.println("Result: " + processOrder(null)); 26 } 27}
Output:
Starting transaction for: ORD-9821
Order processed: ORD-9821
Transaction closed — audit log updated
Result: SUCCESS

Starting transaction for: null
Processing failed: Order ID required
Transaction closed — audit log updated
Result: FAILED

try-with-resources

Introduced in Java 7, try-with-resources automatically closes any resource that implements AutoCloseable when the try block exits — whether normally or due to an exception. This eliminates the common bug where finally blocks forgot to close connections, or where the close call itself throws and swallows the original exception.

Java
1// File: TryWithResourcesDemo.java 2 3import java.io.BufferedReader; 4import java.io.FileReader; 5import java.io.IOException; 6 7public class TryWithResourcesDemo { 8 9 public static String readFirstLine(String filePath) throws IOException { 10 // BufferedReader implements AutoCloseable — close() is called automatically 11 // at the end of this try block, even if an exception is thrown 12 try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { 13 return reader.readLine(); 14 } 15 // No finally needed — reader.close() is called by the compiler-generated code 16 } 17 18 // Multiple resources — closed in reverse order of declaration (reader2 first, then reader1) 19 public static void readMultipleFiles(String path1, String path2) throws IOException { 20 try (BufferedReader reader1 = new BufferedReader(new FileReader(path1)); 21 BufferedReader reader2 = new BufferedReader(new FileReader(path2))) { 22 System.out.println("File 1: " + reader1.readLine()); 23 System.out.println("File 2: " + reader2.readLine()); 24 } 25 } 26 27 public static void main(String[] args) { 28 try { 29 String line = readFirstLine("config.properties"); 30 System.out.println("Read: " + line); 31 } catch (IOException exception) { 32 System.out.println("Could not read file: " + exception.getMessage()); 33 } 34 } 35}
Output:
Could not read file: config.properties (No such file or directory)

throw and throws

throw is the statement that creates and sends an exception at a specific point in code. throws is the declaration on a method signature that announces which checked exceptions the method might propagate to callers. These are different: throw is an action; throws is a contract.

Java
1// File: ThrowThrowsDemo.java 2 3import java.io.IOException; 4 5public class ThrowThrowsDemo { 6 7 // throws: tells callers this method may propagate IOException 8 // without handling it internally 9 public static String fetchConfig(String key) throws IOException { 10 if (key == null || key.isBlank()) { 11 // throw: creates and dispatches a new exception immediately 12 throw new IllegalArgumentException("Config key must not be blank"); 13 } 14 if (key.equals("missing-key")) { 15 // Checked exception — declared in throws clause above 16 throw new IOException("Configuration not found for key: " + key); 17 } 18 return "value-for-" + key; 19 } 20 21 public static void main(String[] args) { 22 String[] keys = {"db.host", null, "missing-key"}; 23 24 for (String key : keys) { 25 try { 26 String value = fetchConfig(key); 27 System.out.println(key + " = " + value); 28 } catch (IllegalArgumentException exception) { 29 System.out.println("Bad input: " + exception.getMessage()); 30 } catch (IOException exception) { 31 System.out.println("Config error: " + exception.getMessage()); 32 } 33 } 34 } 35}
Output:
db.host = value-for-db.host
Bad input: Config key must not be blank
Config error: Configuration not found for key: missing-key

How Exception Handling Works Internally

When an exception is thrown, the JVM performs stack unwinding — a process of walking back through the call stack to find a matching exception handler.

STACK UNWINDING — step by step:

  Call stack before exception:
    main()
      └── placeOrder()
            └── validatePayment()
                  └── callBankAPI()  ← exception thrown here

  JVM searches for handler starting at the top:
    callBankAPI()  — no try-catch → pop frame, propagate up
    validatePayment() — no try-catch → pop frame, propagate up
    placeOrder()   — has catch(BankException) → match found → execute catch
    main()         — not reached

  If NO handler exists at any level:
    Thread's uncaught exception handler is called
    Default: print stack trace, terminate the thread

The exception object travels up the call stack carrying a stack trace — the full ordered list of method frames from where the exception was created to where it was caught (or where the thread ended). This is what you see in logs and what makes debugging possible.

Performance note: creating an exception object is expensive compared to a normal return — the JVM fills the stack trace at creation time, not at print time. A method that throws thousands of exceptions per second is a genuine performance problem, not just a correctness problem. This is why exceptions should represent exceptional conditions, not normal control flow.

Real-World Example — Zomato Order API Error Handling

A food ordering API must handle many failure modes: invalid requests from the client, missing restaurants, payment processing failures, and unexpected system errors. Each category deserves a different HTTP response code and a different recovery strategy. Good exception handling is what separates a production API from one that leaks stack traces to users.

Java
1// File: OrderException.java 2 3public class OrderException extends RuntimeException { 4 5 private final String errorCode; 6 7 public OrderException(String errorCode, String message) { 8 super(message); 9 this.errorCode = errorCode; 10 } 11 12 public OrderException(String errorCode, String message, Throwable cause) { 13 super(message, cause); 14 this.errorCode = errorCode; 15 } 16 17 public String getErrorCode() { 18 return errorCode; 19 } 20}
Java
1// File: RestaurantNotFoundException.java 2 3// Checked exception — callers must handle or declare it 4// Signals a recoverable, expected failure: user searched for a closed restaurant 5public class RestaurantNotFoundException extends Exception { 6 7 private final String restaurantId; 8 9 public RestaurantNotFoundException(String restaurantId) { 10 super("Restaurant not found or currently unavailable: " + restaurantId); 11 this.restaurantId = restaurantId; 12 } 13 14 public String getRestaurantId() { 15 return restaurantId; 16 } 17}
Java
1// File: PaymentFailureException.java 2 3// Unchecked exception — wraps a third-party payment gateway failure 4// The calling code does not need to declare it but can handle it specifically 5public class PaymentFailureException extends RuntimeException { 6 7 private final String paymentGateway; 8 private final int gatewayStatusCode; 9 10 public PaymentFailureException( 11 String paymentGateway, int statusCode, String message, Throwable cause) { 12 super(message, cause); 13 this.paymentGateway = paymentGateway; 14 this.gatewayStatusCode = statusCode; 15 } 16 17 public String getPaymentGateway() { return paymentGateway; } 18 public int getGatewayStatusCode() { return gatewayStatusCode; } 19}
Java
1// File: OrderService.java 2 3public class OrderService { 4 5 // Checked exception in throws clause — callers know they must handle it 6 public OrderResult placeOrder( 7 String restaurantId, String userId, double amount) 8 throws RestaurantNotFoundException { 9 10 validateRequest(userId, amount); // throws OrderException (unchecked) 11 12 Restaurant restaurant = findRestaurant(restaurantId); // throws checked 13 14 return processPayment(userId, restaurant, amount); // throws unchecked 15 } 16 17 private void validateRequest(String userId, double amount) { 18 if (userId == null || userId.isBlank()) { 19 throw new OrderException("INVALID_USER", "User ID is required"); 20 } 21 if (amount <= 0) { 22 throw new OrderException("INVALID_AMOUNT", 23 "Order amount must be positive, received: " + amount); 24 } 25 } 26 27 private Restaurant findRestaurant(String restaurantId) 28 throws RestaurantNotFoundException { 29 if (restaurantId == null || restaurantId.startsWith("CLOSED-")) { 30 throw new RestaurantNotFoundException(restaurantId); 31 } 32 return new Restaurant(restaurantId, "Active Restaurant"); 33 } 34 35 private OrderResult processPayment( 36 String userId, Restaurant restaurant, double amount) { 37 if (amount > 50_000) { 38 // Wraps a simulated external gateway failure 39 RuntimeException gatewayError = 40 new RuntimeException("Gateway timeout after 5000ms"); 41 throw new PaymentFailureException("RAZORPAY", 504, 42 "Payment gateway timed out for amount: Rs." + amount, gatewayError); 43 } 44 return new OrderResult("ORD-" + System.currentTimeMillis(), 45 "CONFIRMED", restaurant.name(), amount); 46 } 47 48 record Restaurant(String id, String name) {} 49 record OrderResult(String orderId, String status, String restaurant, double amount) {} 50}
Java
1// File: OrderController.java 2 3public class OrderController { 4 5 private final OrderService orderService = new OrderService(); 6 7 // Each exception type produces a different HTTP status and message 8 public void handleOrderRequest( 9 String restaurantId, String userId, double amount) { 10 11 System.out.printf("Request: restaurant=%s user=%s amount=Rs.%.0f%n", 12 restaurantId, userId, amount); 13 14 try { 15 OrderService.OrderResult result = 16 orderService.placeOrder(restaurantId, userId, amount); 17 System.out.printf(" HTTP 200 — Order confirmed: %s%n", result.orderId()); 18 19 } catch (OrderException exception) { 20 // 400 Bad Request — client sent invalid data 21 System.out.printf(" HTTP 400 — Validation failed [%s]: %s%n", 22 exception.getErrorCode(), exception.getMessage()); 23 24 } catch (RestaurantNotFoundException exception) { 25 // 404 Not Found — a known, expected business failure 26 System.out.printf(" HTTP 404 — %s%n", exception.getMessage()); 27 28 } catch (PaymentFailureException exception) { 29 // 502 Bad Gateway — downstream service failed 30 System.out.printf(" HTTP 502 — Payment via %s failed (gateway status %d): %s%n", 31 exception.getPaymentGateway(), 32 exception.getGatewayStatusCode(), 33 exception.getMessage()); 34 35 } catch (Exception exception) { 36 // 500 Internal Server Error — unexpected failure 37 // Log with full stack trace internally; surface a safe message to the user 38 System.out.printf(" HTTP 500 — Unexpected error (logged): %s%n", 39 exception.getClass().getSimpleName()); 40 } 41 } 42 43 public static void main(String[] args) { 44 OrderController controller = new OrderController(); 45 46 controller.handleOrderRequest("REST-101", "user-priya", 349.0); 47 System.out.println(); 48 49 controller.handleOrderRequest("REST-101", null, 349.0); 50 System.out.println(); 51 52 controller.handleOrderRequest("REST-101", "user-rohan", -50.0); 53 System.out.println(); 54 55 controller.handleOrderRequest("CLOSED-REST-999", "user-ananya", 599.0); 56 System.out.println(); 57 58 controller.handleOrderRequest("REST-101", "user-karan", 75_000.0); 59 } 60}
Output:
Request: restaurant=REST-101 user=user-priya amount=Rs.349
  HTTP 200 — Order confirmed: ORD-1718012345678

Request: restaurant=REST-101 user=null amount=Rs.349
  HTTP 400 — Validation failed [INVALID_USER]: User ID is required

Request: restaurant=REST-101 user=user-rohan amount=Rs.-50
  HTTP 400 — Validation failed [INVALID_AMOUNT]: Order amount must be positive, received: -50.0

Request: restaurant=CLOSED-REST-999 user=user-ananya amount=Rs.599
  HTTP 404 — Restaurant not found or currently unavailable: CLOSED-REST-999

Request: restaurant=REST-101 user=user-karan amount=Rs.75000
  HTTP 502 — Payment via RAZORPAY failed (gateway status 504): Payment gateway timed out for amount: Rs.75000.0

Custom Exceptions

Custom exceptions communicate domain-specific failure modes clearly. OrderException, RestaurantNotFoundException, PaymentFailureException in the example above each carry fields that generic exceptions cannot. A PaymentFailureException that knows the gateway name and status code is infinitely more useful in logs and monitoring than a RuntimeException with a string message.

The decision between checked and unchecked custom exceptions comes down to recoverability:

  • Make a custom exception checked when the caller can and should recover — a restaurant being closed is expected, and the caller should have a strategy (show the user a different restaurant). RestaurantNotFoundException extends Exception.
  • Make it unchecked when it signals a programming error or system failure that propagates to a global handler — invalid argument validation, payment gateway crashes. OrderException extends RuntimeException.

Custom exceptions should always include a cause when wrapping third-party failures: new PaymentFailureException("RAZORPAY", 504, message, originalCause). Wrapping without preserving the original cause is a common mistake that makes production debugging miserable — the root cause disappears from the stack trace.

Best Practices

Catch the most specific exception type available. A catch (Exception e) block that handles everything says nothing useful about what went wrong. It makes logs ambiguous and prevents specific recovery logic. Catch FileNotFoundException before IOException, IOException before Exception. During code reviews, a catch-all block at the controller layer with proper logging is acceptable — everywhere else, it is a flag.

Never swallow exceptions silently. catch (Exception e) {} — an empty catch block — is one of the most harmful patterns in Java code. The program continues in an unknown state with no record of what failed. Always log the exception, rethrow it, or both. A minimum is logger.error("Unexpected failure", e).

Preserve the original cause when wrapping exceptions. Pass the original exception as the cause argument to the constructor: throw new ServiceException("message", originalException). Without this, the root cause is lost when the exception is logged. Production debugging without the original cause often requires a full rollback to reproduce.

Do not use exceptions for normal control flow. Throwing an exception to signal "item not found" in a method that is called in a tight loop is a performance problem — exception object creation is expensive. Return Optional<T>, return null with a null check, or return an empty collection. Exceptions are for abnormal, unexpected conditions.

Use try-with-resources for any resource that implements AutoCloseable. Database connections, HTTP clients, file handles, streams — all implement AutoCloseable. The old try-catch-finally pattern for closing resources has two known failure modes: forgetting to close, and suppressing the original exception when the close method also throws. try-with-resources handles both automatically.

Common Mistakes

Mistake 1 — Catching Exception Too Broadly Too Early

Java
1// WRONG — catches everything, hides the real problem 2public String getUserData(String userId) { 3 try { 4 return database.query("SELECT * FROM users WHERE id = ?", userId); 5 } catch (Exception exception) { 6 return null; // silently returns null — caller has no idea why 7 } 8} 9 10// CORRECT — let specific exceptions propagate or handle them meaningfully 11public String getUserData(String userId) throws DatabaseException { 12 try { 13 return database.query("SELECT * FROM users WHERE id = ?", userId); 14 } catch (SQLException sqlException) { 15 throw new DatabaseException("User lookup failed for: " + userId, sqlException); 16 // Wraps with cause — original SQL error is preserved in stack trace 17 } 18}

Mistake 2 — Declaring throws Exception on Every Method

Java
1// WRONG — overly broad throws declaration tells callers nothing useful 2public void processPayment(String orderId) throws Exception { 3 // what kind of Exception? callers cannot know without reading the body 4} 5 6// CORRECT — declare the actual exception types 7public void processPayment(String orderId) 8 throws PaymentFailureException, OrderNotFoundException { 9 // callers know exactly what to handle and can make informed decisions 10}

Mistake 3 — Using Exceptions for Flow Control

Java
1// WRONG — exceptions used as conditional logic 2// Exception creation is expensive; this degrades performance in loops 3public boolean isValidProductId(String id) { 4 try { 5 Integer.parseInt(id); 6 return true; 7 } catch (NumberFormatException ignored) { 8 return false; // using exception as an if-else 9 } 10} 11 12// CORRECT — validate without exception 13public boolean isValidProductId(String id) { 14 if (id == null || id.isBlank()) return false; 15 for (char ch : id.toCharArray()) { 16 if (!Character.isDigit(ch)) return false; 17 } 18 return true; 19}

Mistake 4 — Swallowing the Original Cause

Java
1// WRONG — original exception is lost; root cause disappears from logs 2public void saveOrder(Order order) throws ServiceException { 3 try { 4 repository.save(order); 5 } catch (DataAccessException databaseException) { 6 throw new ServiceException("Order save failed"); // cause not passed! 7 } 8} 9 10// CORRECT — always pass the original exception as cause 11public void saveOrder(Order order) throws ServiceException { 12 try { 13 repository.save(order); 14 } catch (DataAccessException databaseException) { 15 throw new ServiceException("Order save failed", databaseException); 16 // Full chain preserved: ServiceException → DataAccessException → SQL error 17 } 18}

Interview Questions

Q1. What is exception handling in Java and why is it needed?

Exception handling is Java's mechanism for responding to runtime errors without crashing the program. When an abnormal condition occurs — invalid input, missing resource, network failure — the JVM creates a Throwable object describing the failure and begins looking for code that handles it. Without exception handling, any runtime error would terminate the JVM immediately. With it, the program can log the failure, return a meaningful response to the caller, attempt recovery, or cleanly shut down specific operations while the rest of the system continues.

Q2. What is the difference between checked and unchecked exceptions?

Checked exceptions extend Exception directly (not through RuntimeException). The compiler enforces handling — if a method can throw a checked exception and the caller does not catch it or declare throws, the code does not compile. They represent expected, recoverable failures: file not found, network timeout. Unchecked exceptions extend RuntimeException. The compiler does not require explicit handling — they typically signal programming mistakes like null dereference or illegal argument. The design philosophy: checked exceptions force callers to acknowledge recoverable failures; unchecked exceptions propagate freely when a programming error should be fixed rather than caught.

Q3. What is the purpose of the finally block and when does it not execute?

finally ensures a block of code runs regardless of whether the try block succeeded or threw an exception — and even if a return statement was executed inside try or catch. It exists for cleanup that must always happen: closing database connections, releasing locks, flushing buffers. The two situations where finally does not execute: System.exit() is called before finally is reached, or the JVM itself crashes (power failure, fatal error). In modern Java, try-with-resources handles most cleanup automatically for AutoCloseable resources.

Q4. What is the difference between throw and throws in Java?

throw is a statement that creates and dispatches an exception at a specific point in code: throw new IllegalArgumentException("message"). throws is a declaration in a method signature that announces which checked exceptions the method may propagate without handling: public void save() throws IOException. They serve different purposes: throw is the action of raising an exception; throws is the contract telling callers what checked exceptions to expect.

Q5. How does exception propagation work in Java?

When a throw statement executes, the JVM stops normal execution and begins unwinding the call stack. It checks the current method for a matching catch block — if none is found, the current stack frame is popped and the exception propagates to the calling method. This continues up the stack until either a matching handler is found or the stack is exhausted. If no handler is found at any level, the thread's uncaught exception handler runs — the default behaviour prints the stack trace and terminates the thread. Interviewers at product companies often ask this to understand if you know what "stack unwinding" means and what happens when no handler exists.

Q6. Why should you create custom exceptions instead of using built-in ones?

Custom exceptions communicate domain-specific failure modes that generic exceptions cannot. PaymentGatewayTimeoutException with a gatewayName field and an httpStatusCode is infinitely more useful in monitoring and debugging than RuntimeException("gateway failed"). Custom exceptions also allow callers to write specific catch blocks for each failure type, enabling different recovery strategies per failure mode. They should carry the data relevant to the failure — never just a string message — and should always pass the original cause when wrapping third-party exceptions.

Q7. What is try-with-resources and how does it differ from try-finally?

try-with-resources automatically calls close() on any AutoCloseable resource declared in the try parentheses when the block exits, whether normally or due to an exception. The traditional try-finally pattern has two failure modes: forgetting to add the close() call in finally, and exception suppression — if the try throws and then finally also throws, the original exception is silently replaced by the finally exception. try-with-resources handles the suppression problem correctly: the secondary exception from close() is attached to the primary exception as a suppressed exception, not replacing it. Prefer try-with-resources for all resource management.

FAQs

What happens if an exception is not caught in Java?

If no matching catch block is found anywhere in the call stack, the exception reaches the thread's uncaught exception handler. The default handler prints the exception type, message, and full stack trace to standard error, then terminates the affected thread. If that thread is the main thread, the entire JVM process exits with a non-zero status code.

Can you catch multiple exception types in one catch block?

Yes, using the multi-catch syntax introduced in Java 7: catch (IOException | SQLException exception). Both types are handled by the same block. The exception variable is effectively final in a multi-catch — you cannot reassign it. This is useful when two different exceptions require identical handling and you want to avoid duplicating the catch block.

What is the difference between Error and Exception in Java?

Both extend Throwable, but they represent fundamentally different problems. Exception represents application-level failures that code can and should handle. Error represents JVM-level conditions — OutOfMemoryError, StackOverflowError — that are typically unrecoverable and outside the application's control. Catching Error is almost always wrong. The one narrow exception: some systems catch OutOfMemoryError to attempt a graceful shutdown rather than an abrupt crash.

Should I catch RuntimeException in my code?

Rarely. RuntimeException subclasses usually signal programming mistakes that should be fixed rather than swallowed — null references, invalid arguments, bad array indices. Catching them masks bugs rather than fixing them. The legitimate cases: at the outermost layer of an application (REST controller, message consumer, batch job) where you want to log the failure and return a safe error response rather than leaking a stack trace to users.

What does it mean to rethrow an exception?

Rethrowing means catching an exception and then throwing it again — either the same object (throw exception) or wrapped in a new exception (throw new ServiceException("message", exception)). Rethrowing preserves the original context while potentially adding domain-specific information. Wrapping without rethrowing the original as a cause destroys the root cause information that makes production debugging possible.

How is exception handling different in Java 8+ Streams?

Lambda expressions used inside Stream pipelines cannot throw checked exceptions without workarounds — the functional interfaces (Function, Consumer, Predicate) do not declare throws. Common approaches: wrap the throwing call in a helper method that catches the checked exception and rethrows it as an unchecked exception, or use a library like ThrowingFunction that provides checked-exception-aware functional interfaces. This is a known friction point in Java's design that is worth understanding before your first code review.

Summary

Exception handling gives Java programs a structured way to detect, describe, and respond to runtime failures. Every exception is a Throwable object — either an Error (JVM-level, do not catch) or an Exception (application-level, handle appropriately). The checked versus unchecked distinction determines what the compiler enforces: checked exceptions represent recoverable failures the caller must acknowledge; unchecked exceptions represent programming mistakes that should propagate.

The five constructs — try, catch, finally, throw, throws — work together to form a contract between methods and their callers about how failures are communicated. Custom exceptions with domain-relevant fields and preserved causes are what separate production-quality error handling from tutorial-level try-catch blocks.

Two rules that senior developers enforce in every code review: never swallow exceptions silently, and always preserve the original cause when wrapping. Both violations make production debugging dramatically harder and are among the most common mistakes in fresher pull requests.

What to Read Next

TopicLink
How try, catch, and finally work together with detailed syntax and edge casesJava try-catch →
What finally guarantees and when it does and does not executeJava finally →
How throw and throws work and when to use each in method signaturesJava throw and throws →
When and how to build custom exception classes for your domainJava Custom Exceptions →
The full difference between checked and unchecked exceptions and when to use eachJava Checked vs Unchecked Exceptions →
What is Exception Handling in Java? | DevStackFlow