A release build shrunk by R8 crashes with frames like at a.c.a(SourceFile:3). The names are gone, and the line numbers point into methods that were merged or inlined. To read the trace you run it through retrace with the mapping.txt R8 wrote for that exact build. Normally that means finding the Android SDK command-line tools, or a JDK and an r8.jar.
This page does the same job in the browser. Paste the trace, choose the mapping file, press Retrace. The retracing code is a port of R8’s own retrace, and its output is compared line for line with Google’s retrace on our test traces (details below). The mapping is read on your device and never uploaded.
What it does
Input.
- The stack trace, pasted as it is: a JVM
printStackTrace(), a logcat crash with itsE AndroidRuntime:prefix, or a copy from the Play Console or Crashlytics. Exception headers,Caused by:,Suppressed:,... 12 moreand lines retrace does not understand all stay in place. - The mapping file of the build that crashed. With the Android Gradle plugin it is
app/build/outputs/mapping/<variant>/mapping.txt. An.aabalso carries it, atBUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map. Mapping files of large apps run to hundreds of megabytes. The file is read as a stream, and only the classes your trace mentions are kept in memory.
Output. The trace with original names, as retrace mapping.txt trace.txt would print it:
- Classes, methods, source files and line numbers are restored. A frame like
(SourceFile:4)becomes(Main.java:52). If the mapping does not name a source file, it is inferred from the class name, as retrace does:Foo$BargivesFoo.java,UtilsKtgivesUtils.kt. - Inlined frames are expanded. When R8 inlined
Parser.digits()intoreadToken()and that intoparse(), one obfuscated frame turns back into all of them, innermost first. In our fixture, 38 input lines became 48. - Ambiguous frames show every candidate, each extra one marked
<OR>. That happens when the trace has no line number and two methods share an obfuscated name, or when one obfuscated line range maps to several original lines. - Compiler-generated frames are removed: synthesized lambda bridges, R8 outlines (their line number is carried into the caller using the
outlineCallsitetable), and frames arewriteFramerule says to drop. For example, R8 drops the inlined callee frame under aNullPointerExceptionthat was really thrown at the call site, because the receiver was null. - Exception class names in header lines are renamed (
a.g: token expiredbecomescom.example.app.ObfuscatedException: token expired).
Changed lines are highlighted, <OR> alternatives and inline frames are marked, and a summary counts retraced, inlined, ambiguous and removed frames. Copy puts the result on the clipboard; Download .txt saves it.
The Try a sample button loads a real trace and mapping. They come from a small Java program we wrote and compiled with R8 9.5.26 in class-file mode, then ran on a JVM.
How it works
The mapping format is line based. com.example.Parser -> a.e: starts a class, and member lines such as 3:6:int digits(java.lang.String):51:54 -> a map obfuscated lines 3 to 6 of method a to original lines 51 to 54. Consecutive lines with the same obfuscated range form an inline group. JSON comments add the rest: the map version, sourceFile, synthesized, outline, outlineCallsite, rewriteFrame and residualsignature.
The tool first parses the pasted trace with retrace’s default regular expression. That gives it the obfuscated class names it needs. It then streams the mapping through a reader that keeps those classes in full and only the source file name of every other class. An inlined frame’s holder can be a class the trace never names, so those names are still needed. Each frame is then looked up the way R8’s retrace does it: narrow the method’s ranges by line number, fall back to ranges without line numbers, split the result into one candidate per signature and per original line, apply outline and rewrite rules, sort the candidates and join them with <OR>.
The code follows R8’s classes one function at a time (ProguardMapReader, StackTraceRegularExpressionParser, RetraceMethodResultImpl, RetraceFrameResultImpl, StringRetrace). R8’s behaviour is kept even where it surprises. One case: in a.b: not positive: -4 the default expression takes -4 as the class name, so the line is left unchanged and a.b is not renamed. Google’s retrace prints the same line. Mapping lines that R8’s reader rejects stop the run with the line number.
How we checked it against Google’s retrace
Every test fixture was run through java -cp r8-9.5.26.jar com.android.tools.r8.retrace.Retrace mapping.txt trace.txt. Our output has to be byte-identical, and the unit tests fail otherwise:
- Real traces: the Java test program compiled by R8 9.5.26 to class files, run on JDK 17, eight exceptions with causes and inlined chains (48 retraced lines).
- Sweeps: for every obfuscated method in four mappings, a frame for every range boundary, the lines just outside each range,
0,65535, no line,Unknown SourceandNative Method, interleaved with exception headers. The four mappings are R8 class-file output, R8 dex output for minimum API 26, a hand-written map with outlines,rewriteFrame, synthesized lambdas and ambiguous ranges, and a ProGuard-style map with no version header (963 lines in total). - Line formats: the same frame with logcat prefixes,
app//class loader prefixes,java.base@17/module prefixes,~[app.jar:?]suffixes,r8-map-id-…and.ktfile names, line0,-1and overflowing line numbers.
Outside the committed tests, we also compared the output with R8 9.5.26 on these inputs:
- the 63 stack trace cases of R8’s own retrace test suite: all identical;
- sweeps over mappings R8 wrote for Guava 33.3.1 and kotlin-stdlib 2.0.21. That is 0.85 MB in class-file mode and 1.15 MB in dex mode, the dex map with 131 outline and
rewriteFrameentries: about 68,500 lines, all identical; - a 143.5 MiB mapping (1.58 million lines). With a 3,000-line trace, the whole run took under half a second in Chromium and in WebKit (Playwright on an Apple-silicon Mac), and the output was identical.
The Android SDK’s own cmdline-tools/latest/bin/retrace (R8 9.0.32) produces the same files.
How your files stay private
Your mapping and your stack trace are not uploaded:
- The mapping is read with
Blob.stream()inside a Web Worker on your device, and the trace is posted to that worker. Nothing is sent anywhere else. - Trace lines and names from the mapping are inserted as plain text; nothing is interpreted as HTML or run.
- Analytics events are coarse: success or an error code, the number of frames and of mapped frames as a bucket (
0,1-10,11-100…), a size bucket of the mapping and a duration bucket. They never contain a class, method or file name, a line of the trace or anything from the mapping. The full list is on the privacy page. - The tool page shows no ads.
How to verify this yourself
- Open your browser’s developer tools and switch to the Network tab before you choose a mapping. Retrace a trace. The only requests are page assets and the worker script
/_astro/retrace-worker-*.js; none of them carries your file or your trace. - Or load the page, go offline (turn off Wi-Fi, or tick Offline in the Network tab) and retrace. It still works.
- The worker runs under the Content Security Policy
default-src 'none'; script-src 'self'; connect-src 'none', so the browser does not let it make any network request.
Limitations
- The mapping must belong to the build. Every release build gets a new mapping. A mapping from a different build still produces plausible names, but they are wrong. If 0 of N class names found shows up in the summary, you have the wrong file.
- Default line format only. Command-line retrace accepts
--regexfor custom log formats and--verboseto print full signatures; neither is supported here. Frames must look likeat class.method(file:line)somewhere in the line. - Fields and types are not retraced. Retrace’s default expression only covers frames and class names, so obfuscated names inside free-text messages (
Attempt to invoke virtual method 'a.b.c()') stay as they are. The same is true of command-line retrace. - Native frames (
#00 pc 0004b1c8 /data/app/.../lib/arm64/libapp.so) are not symbolized. That needs the unstripped.soandndk-stack. See debugging crashes with adb logcat. - Very long lines. Lines over 4,096 characters are passed through without being parsed, to keep the regular expression fast. Command-line retrace would still parse them.
FAQ
Why do some frames show <OR>?
The obfuscated frame fits more than one original location. Usually the trace has no line number (Unknown Source, SourceFile with no number) and several methods were renamed to the same name. Or the mapping says that obfuscated lines 1 to 2 came from original lines 60 to 65, so any of them is possible. R8’s retrace prints all candidates; so does this page.
My trace says Unknown Source:12 instead of a file name. Does that work?
Yes. Only the line number matters for the lookup. Newer R8 versions may also emit instruction offsets or 0:65535 catch-all ranges instead of real line numbers, and retrace turns those back into source lines when the mapping has the information.
Where do I get mapping.txt for a build that is already on Google Play?
From the build output you archived, or from the .aab you uploaded (BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map). In the Play Console, the deobfuscation file is listed with the app version in the app bundle explorer.
Is this the same as Crashlytics or Play Console deobfuscation?
It answers the same question. Those services retrace on their servers with the mapping you upload; this page does it locally, which is useful for traces from logcat, testers or bug reports that never reached those services.
Compliance
Use it for apps you build or maintain, open-source projects, CTF challenges or software you are authorized to analyze. See the disclaimer.
Related guides
- Deobfuscate Android Stack Traces with R8 mapping.txt and Retrace: where mapping.txt comes from, how to read its line ranges and inline groups, and the SDK
retracecompared with this page. - Debug Android App Crashes with adb logcat and bugreport: capture the crash, then retrace the Java frames here.
- JADX MCP Server Setup: Decompile APKs with Claude Code and REA: open the retraced class in a decompiler to see what the bytecode does.
- Android APK Analysis with REA: JADX, Apktool and ADB on an APK you are allowed to inspect.
- APK & AAB analyzer: check the release build itself (manifest, signing, 16 KB alignment) before it ships.