Skip to content

Java Exception Handling — Complete Guide

DodaTech Updated 2026-06-20 11 min read

In this tutorial, you'll learn about Java Exception Handling. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Java Exception Handling uses try-catch blocks and a typed throwable hierarchy to recover from errors without crashing or leaking sensitive information.

What You'll Learn

You'll understand the exception hierarchy (checked vs unchecked vs errors), write try-catch-finally blocks, create custom exceptions, use try-with-resources for automatic cleanup, and apply Exception Handling patterns used in production systems. You'll also learn why proper Exception Handling is a security concern.

Why Exception Handling Matters

Unhandled exceptions crash applications, corrupt data, and leak sensitive information through stack traces. In security-sensitive systems — like DodaTech's Durga Antivirus Pro — an unhandled exception during a scan could leave the system in an inconsistent state, skip a threat, or expose internal paths in error messages shown to users. Proper Exception Handling is not just good practice — it's a security requirement.

Real-World Use: File Scanner Error Recovery

Imagine an antivirus scanner processing 100,000 files. One corrupted file shouldn't crash the entire scan. With proper Exception Handling, the scanner logs the error, skips the problematic file, and continues. DodaTech's scan engine catches IOException around each file read, logs the path and error, and increments an error counter — the scan completes with a summary showing 1 error out of 100,000 files.

Exception Handling Learning Path

flowchart LR
  A[Java Basics] --> B[Java OOP]
  B --> C[Java Exception Handling]
  C --> D[Java Collections]
  C --> E[Java Lambdas & Streams]
  D --> F[File I/O & JDBC]
  E --> G[Spring Boot]
  D --> H[Java Concurrency]
  C:::current

  classDef current fill:#3b82f6,color:#fff,stroke:#333,stroke-width:2px

Understanding the Exception Hierarchy

All exceptions in Java are subclasses of Throwable. The hierarchy splits into three categories:

Category Base Class Example Must Handle?
Checked Exception (excluding RuntimeException) IOException, SQLException Yes — compiler forces try-catch or throws
Unchecked RuntimeException NullPointerException, IllegalArgumentException No — but should be prevented or caught
Errors Error OutOfMemoryError, StackOverflowError No — usually unrecoverable

Checked exceptions represent external failures you can recover from — like a missing file or network timeout. Unchecked exceptions represent programming bugs — like accessing a null reference or dividing by zero. Errors represent JVM-level failures that the application typically cannot handle.

Try-Catch-Finally — The Basics

import java.io.*;

public class ExceptionBasics {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("config.properties"));
            String line = reader.readLine();
            System.out.println("First config line: " + line);

        } catch (FileNotFoundException e) {
            System.out.println("ERROR: Config file not found — using defaults");
            System.out.println("Details: " + e.getMessage());

        } catch (IOException e) {
            System.out.println("ERROR: Failed to read config file");
            System.err.println("Cause: " + e.getCause());

        } finally {
            // Always executes — even if an exception is thrown or caught
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                System.err.println("WARNING: Failed to close reader");
            }
        }

        System.out.println("Application continues normally");
    }
}

Expected output (when config.properties is missing):

ERROR: Config file not found — using defaults
Details: config.properties (No such file or directory)
Application continues normally

The finally block is where you put cleanup code — closing files, releasing network connections, or freeing native resources. It always runs, whether an exception was thrown or not.

Try-With-Resources — Automatic Cleanup

Java 7 introduced try-with-resources, which automatically closes resources that implement AutoCloseable. This eliminates the verbose finally block and prevents resource leaks.

import java.io.*;
import java.nio.file.*;

public class TryWithResourcesDemo {
    public static void main(String[] args) {
        Path logFile = Paths.get("server.log");

        // Try-with-resources — resources are closed automatically
        try (BufferedReader reader = Files.newBufferedReader(logFile);
             BufferedWriter writer = Files.newBufferedWriter(
                     Paths.get("server-filtered.log"))) {

            String line;
            int errorCount = 0;
            while ((line = reader.readLine()) != null) {
                if (line.contains("ERROR") || line.contains("FATAL")) {
                    writer.write(line);
                    writer.newLine();
                    errorCount++;
                }
            }
            System.out.println("Filtered " + errorCount + " error lines");

        } catch (NoSuchFileException e) {
            System.out.println("ERROR: Log file not found at " + logFile.toAbsolutePath());
        } catch (IOException e) {
            System.out.println("ERROR: Failed processing log file: " + e.getMessage());
        }

        // Resources (reader, writer) are already closed — no finally needed
    }
}

Expected output (with server.log containing errors):

Filtered 12 error lines

Security benefit: Try-with-resources ensures file handles are always released, even if an exception occurs mid-operation. Leaked file handles can cause "too many open files" errors, which in security tools could mean missing critical logs during an incident. DodaTech uses try-with-resources in every file operation across its tools.

Checked vs Unchecked — When to Use What

import java.io.*;

public class CheckedVsUnchecked {
    public static void main(String[] args) {
        // Unchecked — compiler does NOT require handling
        String input = getUserInput();
        if (input == null) {
            throw new IllegalArgumentException("Input cannot be null");
        }

        // Checked — compiler FORCES handling
        try {
            writeAuditLog(input);
        } catch (IOException e) {
            System.err.println("Audit failed: " + e.getMessage());
            // In security tools, always log audit failures
        }
    }

    static String getUserInput() {
        // Simulate user input — could be null
        return "Device scan initiated";
    }

    // 'throws' declares that this method can throw a checked exception
    static void writeAuditLog(String message) throws IOException {
        try (FileWriter fw = new FileWriter("audit.log", true);
             BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(java.time.LocalDateTime.now() + " — " + message);
            bw.newLine();
        }
    }
}

Expected output (audit.log):

2026-06-20T10:30:00 — Device scan initiated

Rule of thumb: Use checked exceptions for recoverable conditions that callers should handle (file not found, network timeout). Use unchecked exceptions for programming errors that shouldn't happen (null arguments, invalid indices).

Creating Custom Exceptions

Custom exceptions make your code more readable and allow callers to catch specific error types. DodaTech's services use custom exceptions to distinguish between "device not found" (known state) and "database connection failed" (infrastructure issue).

// Custom checked exception
class ScanTimeoutException extends Exception {
    public ScanTimeoutException(String deviceId, long timeoutMs) {
        super("Scan timed out for device " + deviceId + " after " + timeoutMs + "ms");
        this.deviceId = deviceId;
        this.timeoutMs = timeoutMs;
    }

    private final String deviceId;
    private final long timeoutMs;

    public String getDeviceId() { return deviceId; }
    public long getTimeoutMs() { return timeoutMs; }
}

// Custom unchecked exception
class InvalidConfigException extends RuntimeException {
    public InvalidConfigException(String key, String value) {
        super("Invalid configuration: " + key + " = " + value);
    }
}

// Usage
public class CustomExceptionDemo {
    public static void main(String[] args) {
        try {
            scanDevice("Sensor-42");
        } catch (ScanTimeoutException e) {
            System.err.println("Timeout on " + e.getDeviceId());
            System.err.println("Duration: " + e.getTimeoutMs() + "ms");
            // Retry logic would go here
        }
    }

    static void scanDevice(String deviceId) throws ScanTimeoutException {
        String configValue = "8080";  // Simulated config
        if (!configValue.matches("\\d+")) {
            throw new InvalidConfigException("port", configValue);
        }

        // Simulate scan that might time out
        boolean timedOut = Math.random() > 0.5;
        if (timedOut) {
            throw new ScanTimeoutException(deviceId, 5000);
        }
        System.out.println("Scan completed for " + deviceId);
    }
}

Expected output (one possible run):

Timeout on Sensor-42
Duration: 5000ms

Exception Handling Best Practices for Security

import java.io.*;
import java.sql.*;

public class SafeExceptionHandling {
    public static void main(String[] args) {
        try {
            processFile("/etc/passwd");
        } catch (Exception e) {
            // BAD — exposes internal path in production
            // e.printStackTrace();

            // GOOD — log full details internally, show safe message to user
            logError("File processing failed", e);
            System.out.println("An error occurred while processing the file");
        }
    }

    static void processFile(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String content = br.readLine();
            // Process content...
        }
    }

    static void logError(String message, Throwable cause) {
        // In production, write to a secure, access-controlled log
        System.err.println(java.time.LocalDateTime.now() + " — " + message);
        System.err.println("  Exception: " + cause.getClass().getName());
        System.err.println("  Message: " + cause.getMessage());
        // Do NOT log the full stack trace to user-facing output
    }
}

Expected output:

An error occurred while processing the file

Security rules for Exception Handling:

  • Never print stack traces to user-facing output (exposes internal paths and architecture)
  • Log full exception details to secure, access-controlled logs
  • Show user-friendly, generic error messages
  • Don't catch Exception broadly unless you're at the top level — catch specific types
  • Validate and sanitize exception messages that might contain user input

What Is the Difference Between throw and throws?

throw is a keyword used to actually throw an exception object: throw new IOException("message"). throws is a declaration in the method signature that tells callers "this method may throw this exception": void readFile() throws IOException. Use throw to raise exceptions; use throws to declare them.

Common Mistakes Beginners Make

  1. Catching Exception or Throwable too broadly: catch (Exception e) catches everything, including RuntimeException subclasses you probably want to fail on. Always catch the most specific exception type.

  2. Swallowing exceptions with empty catch blocks: catch (IOException e) {} hides errors and makes debugging impossible. At minimum, log the exception.

  3. Not closing resources in finally: Before Java 7, forgetting to close resources in finally caused resource leaks. Use try-with-resources to prevent this automatically.

  4. Throwing Exception in method signatures: Declaring throws Exception forces all callers to handle or re-declare it, making the API painful to use. Throw specific types.

  5. Using exceptions for normal control flow: Throwing exceptions for expected conditions (like "user not found") is expensive and confusing. Use return values or Optional for expected cases.

  6. Logging and rethrowing the same exception: catch (IOException e) { log(e); throw e; } produces duplicate log entries. Either handle it or throw it — not both.

  7. Exposing sensitive data in exception messages: Including file paths, SQL queries, or user data in exception messages can leak information to attackers through error responses.

Practice Questions

  1. What is the difference between checked and unchecked exceptions?
  2. What does finally guarantee?
  3. How does try-with-resources work?
  4. When should you create a custom exception?
  5. What happens if both try and finally throw exceptions?

Answers:

  1. Checked exceptions must be handled or declared (compiler-enforced). Unchecked exceptions (subclasses of RuntimeException) don't require handling — they represent programming errors.
  2. finally always executes after try/catch, regardless of whether an exception was thrown or caught. It runs even if there's a return statement.
  3. Any resource implementing AutoCloseable declared in the try parentheses is automatically closed when the block exits, in reverse declaration order, without needing an explicit finally.
  4. Create custom exceptions when you need to convey specific error info (like error codes or context) and when callers need to handle different failure types differently.
  5. The finally block's exception propagates, and the try block's exception is suppressed. Use addSuppressed() to preserve both, or check the documentation for suppressed Exception Handling.

Challenge

Build a configuration file parser that reads key=value pairs from a file, validates each value according to its expected type (port numbers must be 1-65535, IP addresses must be valid, boolean values must be true/false), and throws custom exceptions (ConfigParseException, ValidationException) with clear messages showing the file, line number, and issue. Include a summary at the end showing how many lines succeeded and how many failed.

Real-World Task

Extend DodaTech's simulated log analyzer to handle partial failures gracefully. Create a batch log processor that reads 1,000 log entries, processes each one, and catches exceptions per entry so one corrupt entry doesn't stop the entire batch. Track success/failure counts and generate a processing report. This is the same pattern used in DodaTech's security log ingestion pipeline.

FAQ

What is the difference between checked and unchecked exceptions in Java?

: Checked exceptions (subclasses of Exception except RuntimeException) must be handled with try-catch or declared with throws in the method signature. Unchecked exceptions (RuntimeException and its subclasses) do not require explicit handling and usually indicate programming bugs.

What happens if an exception is not caught anywhere?

: The exception propagates up the call stack to the JVM's default exception handler, which prints the stack trace and terminates the current thread. In a single-threaded application, this means the program crashes.

Can I have multiple catch blocks for one try?

: Yes, but order matters — catch the most specific exception first, then more general ones. The compiler enforces this: catching Exception before IOException produces a compilation error because IOException is already covered.

Why should I avoid catching Throwable?

: Error subclasses like OutOfMemoryError are typically unrecoverable. Catching Throwable prevents the JVM from performing its normal error handling and can leave the application in an undefined state.

What's Next

Java Generics Guide
Java Lambdas & Functional Programming
Java Streams Guide
Spring Framework Guide

Related topics: Java, Java OOP, Java Collections, JUnit, Spring

Remember: exceptions are your friends — they tell you exactly what went wrong and where. Learn to read stack traces as well as you write code, and your debugging time will drop by 80%.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro