Best examples of Java exception handling examples for modern Java

If you write Java for a living, you already know that exception handling is where clean code goes to live or die. Theory is easy; what developers actually need are concrete examples of Java exception handling examples that mirror real production code, not toy snippets that only catch `ArithmeticException`. In this guide, we’ll walk through practical, opinionated patterns that show how to use `try-catch`, `try-with-resources`, custom exceptions, logging, and modern Java features in realistic scenarios. These examples of Java exception handling examples are written with Java 17+ in mind (and still friendly to older LTS versions), and they focus on patterns you’ll see in APIs, microservices, and data-heavy applications. Along the way, we’ll talk about when to catch, when to rethrow, and when to let an exception bubble up untouched. If you want real examples instead of textbook trivia, you’re in the right place.
Written by
Jamie
Published

Simple but real example of Java exception handling in a service method

Most tutorials start with division by zero. Let’s start with something you’d actually ship: parsing user input in a service method.

public int parseAge(String ageInput) {
    try {
        int age = Integer.parseInt(ageInput);
        if (age < 0 || age > 130) {
            throw new IllegalArgumentException("Age out of realistic range: " + age);
        }
        return age;
    } catch (NumberFormatException ex) {
        // Wrap with a more meaningful message for higher layers
        throw new IllegalArgumentException("Invalid age format: '" + ageInput + "'", ex);
    }
}

This is one of the best examples of Java exception handling examples for beginners because it shows:

  • Catching a low-level NumberFormatException
  • Translating it into a more domain-friendly IllegalArgumentException
  • Preserving the original stack trace as the cause

You’ll see this pattern in real APIs: catch technical details, rethrow something that makes sense to the caller.


I/O and try-with-resources: examples include file and network access

Older Java code is full of finally blocks that try to close streams and sockets. Modern examples of Java exception handling examples should show try-with-resources instead.

public String readConfigFile(Path path) throws IOException {
    StringBuilder content = new StringBuilder();

    try (BufferedReader reader = Files.newBufferedReader(path)) {
        String line;
        while ((line = reader.readLine()) != null) {
            content.append(line).append('\n');
        }
    } // reader is closed automatically here

    return content.toString();
}

This example of Java exception handling does a few important things:

  • Uses try-with-resources so the BufferedReader is closed even if readLine() throws
  • Lets IOException propagate, because callers usually know better how to respond (retry, show error, etc.)
  • Avoids swallowing exceptions or logging them twice

For a more network-oriented variant, consider reading from an HTTP response stream in a microservice client. The pattern is the same: resources are declared in the try header, and Java guarantees they’ll be closed.

public String fetchJson(URI uri) throws IOException, InterruptedException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder(uri).GET().build();

    HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());

    try (InputStream in = response.body();
         InputStreamReader isr = new InputStreamReader(in);
         BufferedReader reader = new BufferedReader(isr)) {

        return reader.lines().collect(Collectors.joining("\n"));
    }
}

Both are strong, real examples of Java exception handling examples that you’ll see in production codebases interacting with files or HTTP.


Custom domain exceptions: examples of turning low-level errors into business signals

In real systems, you rarely want to throw RuntimeException everywhere. Instead, you create custom exceptions that carry business meaning.

public class InsufficientFundsException extends Exception {
    private final BigDecimal balance;
    private final BigDecimal amountRequested;

    public InsufficientFundsException(String message, BigDecimal balance, BigDecimal amountRequested) {
        super(message);
        this.balance = balance;
        this.amountRequested = amountRequested;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public BigDecimal getAmountRequested() {
        return amountRequested;
    }
}

Usage in a service method:

public void withdraw(Account account, BigDecimal amount) throws InsufficientFundsException {
    if (amount.compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgumentException("Withdrawal amount must be positive");
    }

    if (account.getBalance().compareTo(amount) < 0) {
        throw new InsufficientFundsException(
            "Not enough balance to withdraw",
            account.getBalance(),
            amount
        );
    }

    account.debit(amount);
}

This is one of the best examples of Java exception handling examples in a banking or fintech context. It shows how exceptions can carry structured data that higher layers (like REST controllers) can convert into HTTP 400 responses with clear JSON error bodies.


Wrapping checked exceptions in modern Java APIs

Checked exceptions are still a hot debate in 2025, especially in lambda-heavy code. Many modern libraries prefer to wrap checked exceptions in unchecked ones to keep APIs clean.

public List<String> readAllLinesUnchecked(Path path) {
    try {
        return Files.readAllLines(path);
    } catch (IOException ex) {
        throw new UncheckedIOException("Failed to read file: " + path, ex);
    }
}

This example of Java exception handling shows a pragmatic compromise:

  • Callers don’t have to declare throws IOException
  • The root cause is still preserved
  • The exception type (UncheckedIOException) is meaningful and can be handled selectively

You’ll see this pattern in streams and functional-style code, where checked exceptions don’t play nicely with standard functional interfaces.


Logging and rethrowing: avoiding noisy, duplicate logs

A lot of bad examples of Java exception handling examples show catch (Exception e) followed by e.printStackTrace() and nothing else. In production, you’ll want structured logging, minimal noise, and clear ownership of where exceptions get logged.

private static final Logger LOGGER = LoggerFactory.getLogger(PaymentService.class);

public PaymentResult processPayment(PaymentRequest request) {
    try {
        return paymentGateway.charge(request);
    } catch (GatewayTimeoutException ex) {
        // Log and translate for upper layers
        LOGGER.warn("Payment gateway timeout for request id={}", request.id(), ex);
        throw new PaymentTemporaryException("Payment service temporarily unavailable", ex);
    } catch (GatewayAuthenticationException ex) {
        // Log at error level because it indicates misconfiguration
        LOGGER.error("Payment authentication failed: {}", ex.getMessage());
        throw new PaymentConfigurationException("Payment configuration error", ex);
    }
}

Here, examples include:

  • Different log levels for different exception types
  • Translating low-level infrastructure exceptions into domain-specific ones
  • Avoiding broad catch (Exception e) blocks

This is a real example of Java exception handling you’ll see in microservices that talk to external providers.


Controller-level handling: examples include REST error mapping

In Spring-based applications, some of the best examples of Java exception handling examples live in controller advice classes that map exceptions to HTTP responses.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(InsufficientFundsException.class)
    public ResponseEntity<ApiError> handleInsufficientFunds(InsufficientFundsException ex) {
        ApiError error = new ApiError(
            "INSUFFICIENT_FUNDS",
            ex.getMessage(),
            Map.of(
                "balance", ex.getBalance(),
                "requested", ex.getAmountRequested()
            )
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> handleGeneric(Exception ex) {
        ApiError error = new ApiError(
            "INTERNAL_ERROR",
            "An unexpected error occurred",
            Map.of()
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

This example of Java exception handling shows how to centralize error logic instead of sprinkling try-catch blocks in every controller method. It also makes your API error responses predictable for frontend and mobile clients.

For design background on error handling and contracts, resources like the Harvard CS50 materials provide a good conceptual foundation, even if they’re not Java-specific.


Modern pattern matching for exceptions (Java 21+)

By 2024–2025, more teams are moving to Java 21, which means pattern matching features are becoming mainstream. While catch itself doesn’t use pattern matching yet, pattern matching for instanceof simplifies some exception processing.

public String classifyException(Throwable t) {
    if (t instanceof IOException ioEx) {
        return "I/O error: " + ioEx.getMessage();
    } else if (t instanceof SQLException sqlEx) {
        return "Database error: " + sqlEx.getSQLState();
    } else if (t instanceof IllegalArgumentException iae) {
        return "Bad input: " + iae.getMessage();
    }
    return "Unknown error type";
}

This is a lightweight example of Java exception handling that uses newer language syntax to keep code readable. As pattern matching expands, you can expect more expressive error classification without giant if-else chains.


Retry and circuit breaker patterns: real examples from resilient systems

In distributed systems, exceptions often represent transient failures. Instead of giving up instantly, you might retry or use a circuit breaker. Libraries like Resilience4j are popular in 2025 for this.

public String callInventoryService(String productId) {
    return Try.ofSupplier(() -> inventoryClient.getStock(productId))
              .recover(TimeoutException.class, "UNKNOWN")
              .recover(IOException.class, "UNKNOWN")
              .get();
}

Here, examples include:

  • Wrapping calls in library-provided Try abstractions
  • Handling specific exception types with fallback values

If you implement retries manually, exception handling still sits at the core:

public String callWithRetry(Supplier<String> remoteCall, int maxAttempts) {
    int attempts = 0;
    while (true) {
        try {
            return remoteCall.get();
        } catch (TimeoutException | IOException ex) {
            attempts++;
            if (attempts >= maxAttempts) {
                throw new RuntimeException("Remote call failed after " + attempts + " attempts", ex);
            }
            // simple backoff
            try {
                Thread.sleep(200L * attempts);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Retry interrupted", ie);
            }
        }
    }
}

This is one of the more advanced examples of Java exception handling examples, showing how transient exceptions shape control flow and resilience strategies.

For general guidance on resilient system design and fault tolerance, organizations like NIST publish vendor-neutral guidance that aligns well with patterns you see in modern Java ecosystems.


Best practices illustrated by these examples of Java exception handling examples

Looking across these real examples, a few patterns stand out:

  • Catch exceptions where you can add value. If you can’t add context, convert, or recover, let it propagate.
  • Use custom exceptions for domain meaning. InsufficientFundsException tells a much clearer story than RuntimeException.
  • Don’t swallow exceptions. If you catch, either log meaningfully, rethrow, or both.
  • Avoid broad catch (Exception e) unless you’re at a true boundary. Framework edges, main methods, and global handlers are valid spots.
  • Use try-with-resources for anything that needs closing. Streams, readers, database connections, and so on.

These aren’t just theoretical; they come directly from the best examples of Java exception handling examples you’ll see in widely used open-source projects and large enterprise systems.

If you want a deeper academic angle on error handling and reliability, computer science courses at universities like MIT OpenCourseWare and Harvard’s CS programs often discuss exception-like concepts in the context of system reliability and software design.


FAQ: common questions about examples of Java exception handling

What are some simple examples of Java exception handling for beginners?

Good starter examples of Java exception handling include parsing numbers from user input, reading small text files with try-with-resources, and validating method arguments with IllegalArgumentException. The age-parsing and file-reading snippets earlier are perfect beginner-friendly patterns.

When should I create a custom exception instead of using RuntimeException?

Create a custom exception when the error has clear domain meaning and callers might reasonably want to handle that specific case. An example of this is InsufficientFundsException in a banking system or QuotaExceededException in a SaaS platform. If no one will ever catch it specifically, a standard exception type is usually fine.

Is it okay to catch Exception in Java?

Yes, but only at well-defined boundaries: top-level request handlers, main methods, background thread runners, or global error handlers. In the middle of your business logic, prefer catching specific exception types, as shown in the payment and REST controller examples of Java exception handling examples above.

How do I avoid cluttering my code with too many try-catch blocks?

Push exception handling to the edges. Let lower layers throw meaningful exceptions, and centralize handling in service facades, controller advice classes, or infrastructure utilities. The Spring @RestControllerAdvice example of Java exception handling demonstrates this pattern nicely.

Are checked exceptions still a good idea in 2025?

They’re still widely used in the JDK (IOException, SQLException, etc.), but many modern libraries favor unchecked exceptions for cleaner APIs, especially around streams and lambdas. The wrapping pattern with UncheckedIOException is a realistic middle ground. Which approach you choose should match your team’s style and the expectations of your ecosystem.

Explore More Java Code Snippets

Discover more examples and insights in this category.

View All Java Code Snippets