Java error logs were built in the ’90s for a human with grep — and for that reader they work. But the reader is changing: an AI assistant or an automated agent reads an error with no muscle memory, and every line is context and token cost. stacktale captures the failure at the source, structured for that reader — alongside your untouched logs.
The Java stack trace hasn't changed since 1995. The reader has.
For thirty years the format was aimed at one reader: a human at a terminal with grep and a hunch. Then, quietly, the most frequent reader of an error log became an AI assistant — and nobody redesigned the log for it. Every pasted stack trace becomes an interrogation: what happened before? which values? which version? — ten messages of the AI asking for context that existed at the moment of the error and was thrown away.
Post-processing can't fix this — by the time the log is written, the story is already gone. The only place to capture it is at the source. That's the whole idea, and it's why a 1-line stack trace from 1995 and a stacktale report are not the same artifact at all.
A checkout dies on an NPE. The root cause — a cache miss returning null — happened three log lines earlier, buried under other threads.
ERROR o.a.c.c.C.[dispatcherServlet] java.lang.NullPointerException: Cannot invoke "Customer.getEmail()" because "customer" is null at com.shop.OrderService.confirm(OrderService.java:87) at jdk.internal.reflect.DirectMethodHandle... at org.springframework.web.method.support.In... at org.springframework.web.servlet.mvc.method... at org.springframework.web.servlet.Dispatcher... at org.apache.catalina.core.ApplicationFilter... at org.apache.catalina.core.StandardWrapper... … 49 more framework frames … (what happened before? which values? which version? — you'll be asked all of it)
━━━ ERROR #cddfa41c ━━━ thread=exec-5 NullPointerException: "customer" is null at OrderService.confirm(:87) ← YOUR CODE log: "checkout failed for order {}" args=[889] mdc: traceId=70e1dfe7 fields: orderId=889 retryable=false story (traceId=70e1dfe7, last 4 events, 412ms): INFO OrderController POST /orders/889/confirm INFO CustomerClient fetch customer 555 → 404 WARN CustomerCache miss 555, returns null ERROR OrderService checkout failed 889 ← stack (distilled, 2 of 56 frames): OrderService.confirm(:87) ← culprit … 37 collapsed (spring ×24, tomcat ×13) env: app=shop-api 1.4.2 | java 21 | linux ━━━ END #cddfa41c ━━━
Counted with a real tokenizer on the artifacts of an actual dogfooding session — a Spring Boot app hit over HTTP until it failed seven ways.
a classic log grows with traffic · a report file grows only with distinct errors
Captured at the source and never thrown away — because by the time the log is written, the story is already gone.
-javaagent capturing method args at the throw site.<dependency> <groupId>io.github.gabrielbbaldez</groupId> <artifactId>stacktale-spring-boot-starter</artifactId> <version>1.1.0</version> </dependency>
That's it. Reports land in errors-ai.log; your console stays exactly as it was.
The file is the artefact; the loop is the point. Your assistant fixes an error, re-runs the app or the tests, asks stacktale what changed, and repeats until it answers no new errors.
/plugin marketplace add stacktale/stacktale /plugin install stacktale@stacktale
Cursor, Claude Desktop and anything else that speaks MCP: see mcp-setup.md. There are also IntelliJ and VS Code extensions, and a GitHub Action that posts the reports on a red build.