Software Engineer's Blog

Modern Java Exception Handling: Best Practices and Logging Strategies

Modern Java Exception Handling: Best Practices and Logging Strategies

How senior developers handle errors in real production systems

Exception handling is one of the least glamorous topics in software development—yet it is the backbone of reliable, maintainable systems. In the previous post, we discussed why modern Java development favors unchecked exceptions for business logic.
In this follow-up, we’ll look at how to handle, log, and organize those exceptions effectively in real-world Spring Boot applications.

1. Exception Handling Best Practices

✔ 1) Handle exceptions at the correct layer

Not every layer should handle every error:

LayerResponsibility
ControllerDelegate to services; minimal handling
ServiceThrow meaningful business exceptions
RepositoryWrap low-level exceptions
Global Handler (@ControllerAdvice)Logging + final API response

Rule of thumb:
Handle locally only if the issue is recoverable.

Otherwise, propagate upward and let your global handler manage the response.

✔ 2) Do not catch Error

Error represents JVM-level failures (OutOfMemoryError, StackOverflowError).
Catching them is almost always wrong.

✔ 3) Provide meaningful messages

Good exception messages explain:

  • What happened
  • Why it happened
  • What the user or system can do

Bad:

throw new RuntimeException("Failed");

Good:

throw new FileStorageException("Failed to store file: " + filename);

✔ 4) Wrap and chain exceptions properly

When wrapping checked exceptions, always preserve the original cause.

catch (IOException e) {
    throw new FileStorageException("Failed to store file: " + filename, e);
}

This keeps the full stack trace and root cause for debugging.

✔ 5) Use try-with-resources for all I/O

Avoid manual close() calls.

try (var input = Files.newInputStream(path)) {
    return input.readAllBytes();
}

2. Logging Strategy (Real-World, Production Grade)

✔ Log exceptions exactly once

Double logging is one of the worst anti-patterns.

❌ Bad (double-logging everywhere)

  • Repository logs
  • Service logs
  • Controller logs
  • Global handler logs

This floods the system with repeated traces.

✔ Good:

  • Log once at the system boundary (usually the global handler)
  • Internal layers throw, not log

✔ Avoid “log + rethrow” unless you’re certain

Bad:

catch (Exception e) {
    log.error("Error saving user", e);
    throw e;  // ❌ Global handler logs again → duplicates
}

✔ Include context information

A log without context is noise.

Good logging example:

log.error("Failed to process order. orderId={}, userId={}", orderId, userId, e);

Recommended context fields:

  • userId
  • orderId
  • filename
  • clientIp
  • transactionId
  • correlationId

3. Practical Q&A (Real-World Scenarios)

Q1. Should all custom exceptions extend RuntimeException?

Short answer: Usually yes, but not always.

Use RuntimeException for:

  • Business rule violations
  • Domain-level errors
  • Validation logic

Use Checked Exceptions for:

  • External I/O
  • Filesystem, network, DB drivers
  • Recoverable or interruptible operations

Q2. Where should exceptions be handled?

Best practice for Spring Boot:

LayerStrategy
ControllerNo try-catch; delegate
ServiceThrow meaningful custom exceptions
RepositoryWrap external/checked exceptions
Global@ControllerAdvice handles everything

The global exception handler is the single point for:

  • Logging
  • HTTP status mapping
  • Returning standardized error responses

Q3. Should I wrap every exception inside one generic exception?

No.
You lose semantic meaning, and debugging becomes much harder.

Instead, design a clean exception hierarchy.

4. Final Thoughts

Mastering exception handling goes far beyond understanding Java’s hierarchy.
Senior developers focus on:

  • Using checked exceptions only for external resources
  • Using unchecked exceptions for business logic
  • Designing meaningful custom exception classes
  • Avoiding double logging
  • Mapping errors cleanly with @ControllerAdvice
  • Providing helpful, contextual messages

When done right, exceptions become a powerful tool for building clean, reliable, and production-ready applications.