Article
TransactionTooLargeException and a Bridge to Safety — Part 1
February 27, 2019

TL;DR : Stop worrying about TransactionTooLargeException
; use Bridge .
I still remember when I first read the release notes for Android Nougat in the summer of 2016. Buried alongside mostly innocuous changes to things you probably wouldn’t care about was something that jumped out at me:
Many platform APIs have now started checking for large payloads being sent across
Binder
transactions, and the system now rethrowsTransactionTooLargeExceptions
asRuntimeExceptions
, instead of silently logging or suppressing them. One common example is storing too much data inActivity.onSaveInstanceState()
, which causesActivityThread.StopInfo
to throw aRuntimeException
when your app targets Android 7.0.
I would guess that many people glossed over this one, too, and I might have as well if it weren’t for the fact that I’d seen this before. Long ago, I’d seen direct evidence of the OS “silently logging” this problem: I’d noticed some strange state saving behavior and there, buried in all the other logs of the app I was working on, was this singular, aggressively punctuated line:
!!! FAILED BINDER TRANSACTION !!!
And that was it.
After a little investigation, I understood that we were sending too much data in our saved state Bundles. The Bundle couldn’t be sent to the OS for safe keeping, so it was just getting dropped on the floor. For actual users experiencing this situation, the result would be that when the app process was killed while in the background and they returned to it, it would launch from a fresh state rather than a restored one. Less than ideal, sure. But, at the time, it seemed like a graceful enough fallback for the particular edge case I was seeing that “fixing” it became a problem for the backlog.
But then, Nougat happened. I suspected TransactionTooLargeException
would sneak up on a lot of developers, and those suspicions were confirmed when I started seeing all the Stack Overflow questions and “bugs” filed with the Android Issue Tracker, like “TransactionTooLargeException on pausing app” . The message from Google, though, was clear:
Status
Won’t Fix (Intended behavior)
And that’s when I decided to do something about it.
The Problem
Before talking about our solution, let’s first go a little deeper into the actual problem. At its core, Android is just a customized version of Linux in which each application runs in its own separate process. This is true of the core functionality of the operating system itself, which runs in a process very creatively named system_process. In order for an application to function, it needs to communicate with system_process via a special form of inter-process communication (IPC) called “Binder IPC”.

The Binder framework is a fascinating topic that gets to the core of how Android works (and has a history stretching back to Palm OS) but I’ll summarize a few relevant details here:
- Binder IPC allows communication to occur synchronously in each process via a “transact” method. These “Binder transactions” pass data between the processes via highly optimized data containers called Parcel .
- Several familiar Android objects like Intent, Bundle, and Parcelable are ultimately packaged in Parcel objects in order to communicate with system_process.
- Creating / starting / stopping / pausing / resuming / etc. an Android Activity all involve making Binder transactions.
- Each app process has a 1 MB buffer for all Binder transactions.
That last key point is critical : if at any point one of the Parcels becomes so large that its corresponding transaction overflows the 1 MB buffer, we say that the transaction was too large. Hence we get the name, TransactionTooLargeException.

To make matters worse, because the 1 MB limit is on the *buffer* and not on an individual *transaction*, the transactions that actually push things over the limit are typically much smaller and more like ~0.5 MB. Unless you’re simply storing a few primitives here and there, that’s not a lot of data.
This brings us to one of the key places this can all go wrong in an app : onSaveInstanceState
. An example implementation might look something like the following: