Question 28 · Section 7

Can you rethrow an exception?

Rethrow - catching an exception in catch to perform an action (logging) and sending it further:

Language versions: English Russian Ukrainian

Junior Level

Yes, you can!

Rethrow - catching an exception in catch to perform an action (logging) and sending it further:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public void processOrder(Long orderId) {
        try {
            orderRepository.save(orderId);
        } catch (Exception e) {
            log.error("Failed to process order id={}", orderId, e); // Log
            throw e; // Propagate further
        }
    }
}

Why it’s needed

  • Logging - record the error before it goes further
  • Metrics - count the number of errors
  • Cleanup - release resources before propagation
try {
    connection.beginTransaction();
    connection.save(order);
} catch (SQLException e) {
    connection.rollback(); // Rollback transaction
    throw e; // Propagate further
}

Wrapping on rethrow

Often wrapped in another exception:

try {
    repository.save(order);
} catch (SQLException e) {
    throw new OrderException("Failed to save order id=" + order.getId(), e); // New type + context
}

When NOT to use rethrow

  1. Without adding value - catch (Exception e) { throw e; } is useless without logging/metrics
  2. Duplicate logging - if exception is already logged at this level, don’t log again on rethrow
  3. Instead of wrapping - if you need a new exception type, use wrapping with cause, not bare rethrow
  4. In transactions without rollback - rethrow of unchecked exception in @Transactional rolls back transaction, checked - doesn’t
  5. Multiple rethrow - don’t catch/throw the same exception at every layer - one level is enough

Middle Level

Instruction athrow

At the bytecode level, rethrow is a regular athrow instruction. It takes the exception object from the operand stack and searches for a handler in the Exception Table.

Important: on throw e; the stack trace doesn’t change. It shows the original location of occurrence, not the throw e line.

Precise Rethrow (Java 7+)

One of the most useful but rarely discussed features:

public void process() throws IOException, SQLException {
    try {
        if (condition) throw new IOException();
        else throw new SQLException();
    } catch (Exception e) { // Catch common ancestor
        log.error("Intermediary processing");
        throw e; // Compiler "knows" - only IOException or SQLException
    }
}

Before Java 7, you’d have to declare throws Exception. Now the compiler analyzes the contents of try.

Rethrow with wrapping (Exception Chaining)

// Plus: add context of the current layer
throw new MyException("Context: processing order " + orderId, e);

// Minus: new object + new fillInStackTrace()
// Always pass the original exception as cause!

Side Effects in rethrow

In high-load systems, rethrow is used for metrics:

catch (RuntimeException e) {
    metrics.increment("errors.count");
    throw e;
}

Senior Level

Double Traversal

Rethrowing forces JVM to rescan the Exception Table. In deep hierarchies, frequent rethrows create a small CPU load.

Checked to Unchecked

Often checked exceptions are propagated as unchecked - “breaks” obligations in the signature:

try {
    // checked exception
    Files.readAllLines(path);
} catch (IOException e) {
    throw new RuntimeException(e); // Unchecked - method no longer declares throws
}

Sneaky Throws

Trick with propagating checked exception without declaring in throws:

@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
    throw (E) e;
}

Lombok @SneakyThrows uses this same mechanism.

Losing Exceptions

If the catch block called a method that itself threw an exception, and you didn’t propagate the original - the original is lost:

catch (Exception e) {
    log.info("Error");        // OK
    doSomethingThatAlsoThrows(); // New exception! Original e is lost!
    throw e; // Never executes
}

fillInStackTrace() - updating the stack trace

If you want to update the stack trace to the current line (so logs show the rethrow location):

throw (RuntimeException) e.fillInStackTrace();

Only do this if you understand why - the original stack trace will be lost.

Diagnostics

  • Log Analysis - make sure each rethrow is logged with context
  • Metrics - count rethrows via Micrometer
  • Debugger - in IntelliJ expand cause node to see the entire chain
  • javap -c - shows the athrow instruction in bytecode

Interview Cheat Sheet

Must know:

  • Rethrow - catching exception in catch with action (log, metrics, rollback) and propagating further via throw e
  • On throw e; stack trace doesn’t change - shows original error location
  • Checked exceptions can be propagated without losing typing thanks to Precise Rethrow (Java 7+)
  • Wrapping on rethrow adds context: throw new OrderException("msg", e) - always pass cause
  • Rethrow of unchecked in @Transactional rolls back transaction, checked - doesn’t
  • Useless rethrow: catch (Exception e) { throw e; } without logging/metrics/cleanup
  • e.fillInStackTrace() updates stack trace - use consciously, original stack will be lost

Frequent follow-up questions:

  • Does stack trace change on throw e? - No, shows original error location
  • What is Precise Rethrow? - Java 7+: compiler analyzes try and preserves exact types in throws
  • When is rethrow useless? - Without logging, metrics, cleanup - bare catch { throw e; }
  • How to update stack trace? - e.fillInStackTrace() - but original stack will be lost

Red flags (NOT to say):

  • “On rethrow stack trace is updated” - no, only on fillInStackTrace()
  • “Rethrow of checked exception doesn’t rollback @Transactional” - depends on type: unchecked rolls back, checked - doesn’t
  • “Multiple rethrows at every layer is good practice” - one level is enough
  • “Rethrow without cause loses original error” - on throw e without wrapper, error is not lost

Related topics:

  • [[18. What is exception wrapping (wrapping)]] - wrapping on rethrow
  • [[20. What does the throws keyword do]] - Precise Rethrow preserves types
  • [[28. What is exception chaining]] - chaining on wrapping with cause
  • [[21. Can you throw a checked exception from a method without throws]] - sneaky throws alternative
  • [[17. How to properly log exceptions]] - logging before rethrow