API ComparatorOpen Log Explainer

Java debugging

How to read Java stack traces and find the root cause

A Java stack trace looks intimidating because it records every method involved in a failure. You rarely need to read every line. With the right order, you can reduce a long trace to the exception, the true cause, and the first line of code you should inspect.

Updated September 16, 202611 minute read

What a Java stack trace tells you

A stack trace is a snapshot of the active method calls when an exception occurred. It normally contains an exception type, an explanatory message, a list of stack frames, and sometimes one or more nested causes.

java.lang.IllegalStateException: Unable to create order
    at com.example.order.OrderController.create(OrderController.java:42)
Caused by: java.lang.NullPointerException: Cannot invoke "Customer.id()" because "customer" is null
    at com.example.order.OrderService.createOrder(OrderService.java:87)
    at com.example.order.OrderController.create(OrderController.java:39)

This shortened, fictional trace uses example package names and line numbers. In this example, the most useful facts are the deepest Caused by message and the first frame belonging to the application. Framework frames provide context, but they are rarely the first place to edit code.

Step 1: read the exception type and message

Start with the first line. The exception type identifies the category of failure, while the message describes what went wrong in this specific case.

NullPointerException

Code used a reference that was null.

IllegalArgumentException

A method received a value it considers invalid.

ConnectException

A network connection to another service could not be established.

SQLException

A database query, update, connection, or constraint failed.

Do not stop at the type. A detailed message such as customer is null is much more actionable than the generic phrase “null pointer.”

Step 2: follow the “Caused by” chain

Java applications often catch a low-level exception and wrap it in a higher-level one. Each Caused by: section reveals the exception underneath the previous layer.

Read the chain from the bottom upward. The deepest cause is usually closest to the original failure. For example, a Spring BeanCreationException may ultimately be caused by a missing environment variable, invalid database password, or duplicate bean definition.

The deepest cause is the starting point, not automatic proof.

Confirm it using nearby application frames, request data, configuration, and the behavior you can reproduce.

Step 3: find the first frame from your code

Every line beginning with at is a stack frame. It shows the class, method, source file, and line number that were active during the failure.

at com.example.order.OrderService.createOrder(OrderService.java:87)
   └──────── class ────────┘ └─ method ─┘ └── file and line ──┘

Skip past Java, Spring, servlet, proxy, and reflection frames until you find your organization's package name. Open that file at the reported line, then inspect the values used there and the calls immediately before it.

Step 4: inspect the failing line in context

The numbered line is where Java noticed the failure, not always where the bad value originated. Read several lines above it and trace each input backward.

  • Which object, argument, or return value failed?
  • Where was that value created or retrieved?
  • Could a database query or API legitimately return no result?
  • Does one request path skip initialization or validation?
  • Did configuration differ between working and failing environments?

A guard clause can prevent a crash, but it may hide the real defect. Fix the point where invalid or missing data first becomes possible when that behavior violates the application contract.

Step 5: separate the root cause from secondary errors

One failure can generate many later messages: transaction rollbacks, broken response writes, retry failures, or shutdown warnings. Sort entries by timestamp and request or trace ID. The earliest relevant error is often more useful than the loudest final message.

When multiple services are involved, follow the same correlation ID across the gateway, API, downstream service, and database logs. This creates a timeline instead of treating every error as a separate incident.

Common Spring Boot stack traces

BeanCreationException

Read its deepest cause for missing configuration, dependency failures, or code executed during bean initialization.

UnsatisfiedDependencyException

Check which constructor or field dependency Spring could not provide, then inspect qualifiers, profiles, and component scanning.

HttpMessageNotReadableException

The request body could not be parsed. Compare JSON syntax, field types, date formats, and required values with the request model.

DataIntegrityViolationException

A database constraint may have rejected the operation. Read the nested SQL exception for the actual constraint or column.

ResourceAccessException

A network call failed. Look for nested timeout, DNS, TLS, or connection-refused details.

What “... more” and suppressed exceptions mean

Java shortens repeated frames using text such as ... 24 more. It means those frames are identical to frames already printed above; it does not mean that information is missing.

A Suppressed: section records another exception that happened while Java was handling the main one—often when closing a file, stream, or connection. Investigate it when the primary cause does not fully explain the failure, but keep the main exception chain as your first path.

A reliable root-cause workflow

  1. Capture the complete trace with the timestamp and correlation ID.
  2. Read the exception type and specific message.
  3. Move to the deepest relevant Caused by section.
  4. Find the first stack frame from your application package.
  5. Open the reported file and inspect the line in context.
  6. Trace the failing value back to its source.
  7. Reproduce with sanitized input in a safe environment.
  8. Add or update a test that proves the correction.

Worked example: an order fails for a missing customer

Suppose an order request contains customer ID 9999, but the test customer store contains only 1042. The service looks up the requested customer and immediately calls customer.id(). The lookup returns null, so that method call throws a NullPointerException. The controller wraps it with “Unable to create order.”

Paste the trace above into Log Explainer and select Explain this log. You should see “A value was used before it was initialized,” with the nested NullPointerException as the most useful clue. The tool recognizes a null-reference pattern; it cannot know whether the customer was deleted, the wrong tenant was queried, or the lookup itself is defective.

Reading this incident, one clue at a time
EvidenceWhat it establishesNext check
IllegalStateExceptionThe controller reported that order creation failed.Read the nested cause before changing the controller.
customer is nullThe dereferenced customer variable has no object.Inspect the customer lookup result.
OrderService.java:87The failure occurred at this service frame in that build.Open the matching source revision, not a different release.
Only ID 9999 failsThe failure correlates with one input in this scenario.Compare missing and existing IDs using the same test store.

The unsafe code

Customer customer = customers.get(customerId);
return "order-for-" + customer.id();

Returning an empty string or catching every exception here would conceal the missing customer. Decide on the service contract first. In this example, a missing or unknown ID must fail explicitly, and an existing ID must still succeed. A real HTTP API should map that domain outcome to its documented response; Java throwing an exception alone does not choose the right HTTP status.

A complete correction you can run

Save this self-contained example as OrderExample.java. It uses Java 17 or newer and an in-memory test store, so it needs no production data, framework, or database.

import java.util.Map;

public class OrderExample {
    record Customer(String id) {}
    static final Map<String, Customer> CUSTOMERS =
        Map.of("1042", new Customer("1042"));

    static String createOrder(String customerId) {
        if (customerId == null || customerId.isBlank()) {
            throw new IllegalArgumentException("customerId is required");
        }
        Customer customer = CUSTOMERS.get(customerId);
        if (customer == null) {
            throw new IllegalArgumentException("Unknown customer: " + customerId);
        }
        return "order-for-" + customer.id();
    }

    static void expectRejected(String id, String expectedMessage) {
        try {
            createOrder(id);
            throw new AssertionError("Expected rejection for: " + id);
        } catch (IllegalArgumentException ex) {
            if (!expectedMessage.equals(ex.getMessage())) {
                throw new AssertionError("Unexpected message: " + ex.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        if (!"order-for-1042".equals(createOrder("1042"))) {
            throw new AssertionError("Existing customer should succeed");
        }
        expectRejected(null, "customerId is required");
        expectRejected(" ", "customerId is required");
        expectRejected("9999", "Unknown customer: 9999");
        System.out.println("4 checks passed");
    }
}
javac --release 17 OrderExample.java
java OrderExample
# Expected output: 4 checks passed

The checks cover a valid customer, a null ID, a blank ID, and an unknown customer. If your application uses a repository, repeat these cases with a controlled repository stub. Also verify that an unavailable database remains a distinct failure; do not turn every database error into “customer not found.”

A second trace: a wrapper hides a network failure

org.springframework.beans.factory.BeanCreationException: Error creating bean 'paymentClient'
    at com.example.PaymentConfiguration.paymentClient(PaymentConfiguration.java:24)
Caused by: java.net.ConnectException: Connection refused
    at java.base/sun.nio.ch.Net.connect0(Native Method)
    at com.example.PaymentClient.connect(PaymentClient.java:31)

Here the visible Spring error names the bean, but the nested cause says the connection was refused. Log Explainer should select “The target service refused the connection.” Changing component scanning is unlikely to address that evidence.

  1. Check the configured destination host and port in the failing environment. A service listening on 8080 will not answer a client configured for 8082.
  2. Check that the destination process is running and listening. In a container, localhost refers to that container, not another service.
  3. Check service endpoints and network rejection rules. A firewall or proxy can reject a connection too.
  4. Retry one safe test request after correcting the confirmed cause, then verify both the dependency connection and application startup.

A refusal is different from a timeout or TLS handshake error. If the nested message names one of those, follow that evidence instead of applying this refusal checklist blindly.

When the tool cannot decide for you

The explanation uses deterministic patterns in your browser. It does not run an AI model, connect to your system, read source files, or correlate requests across services. For one conventional trace, it selects the last Caused by: line. If you paste several incidents or a suppressed-exception branch, isolate the relevant chain first.

An unknown nested exception receives general guidance. A recognized exception still needs a reproducible input and configuration check. Use the focused NullPointerException guide for unboxing and collection cases that differ from the customer example.

The underlying Java behavior is documented in Oracle’s Throwable reference, including causes, stack frames, and suppressed exceptions. The examples and troubleshooting sequence on this page are original synthetic scenarios.

Share logs safely

Before sending a trace to a teammate or putting it into any analysis tool, remove credentials and personal or production data. Stack traces can include request headers, URLs, database details, filenames, email addresses, tokens, and internal hostnames.

Never paste live secrets.

Remove passwords, API keys, cookies, access tokens, authorization headers, customer records, and confidential business data. Rotate a secret if it was exposed.

Explain your stack trace

Paste a sanitized Java or Spring trace into Log Explainer to identify the error pattern, likely causes, important lines, and the next debugging steps. The analysis runs locally in your browser.

Open Log Explainer