Efficient Object Serialization on Android with Parcelable and Serializable
Passing rich data between Android components relies on turning objects into a transmittable form, typically via Intent and Bundle. Two primary mechanisms exist: Java’s Serializable and Android’s Parcelable. Both serialize state into bytes, but they differ in performance, capabilities, and appropriate use cases.
When to serialize
- Persisting in-memory objects to disk or caches
- Sending objects over sockets or other network channels
- Remote method invocation (e.g., Binder IPC on Android)
- Activity/Service/Broadcast/Provider boundaries via Intent/Bundle
Parcelable in Android Parcelable defines how an object flattens itself into a Parcel and reconstructs from it. It is optimized for Binder IPC and inter-component communication on Android and typically outperforms Serializable.
Contract
- int describeContents(): usually returns 0. Non-zero is reserved for special cases like file descriptors.
- void writeToParcel(Parcel dest, int flags): write fields to the Parcel in a deterministic order.
- static final Parcelable.Creator<T> CREATOR: recreates objects from a Parcel and builds arrays.
Guidelines for choosing
- In-memory IPC and intents: prefer Parcelable for speed and allocation efficiency.
- Long-term persistence or external storage: avoid Parcelable; it is not version-tolerant or stable for storage. Use Serializable or your own stable format (JSON/Proto/etc.).
- Serializable tends to allocate more temporaries and can trigger more GC compared to Parcelable on Android.
Implementing Parcelable: steps
- Implement Parcelable on the class to be transmitted.
- In writeToParcel, write each field in a consistent order.
- Provide a constructor or factory that reads fields in the exact same order.
- Expose a public static final CREATOR implementing Parcelable.Creator<T>.
Example 1: simple Parcelable with null-safe handling
package example.transport;
import android.os.Parcel;
import android.os.Parcelable;
public class Contact implements Parcelable {
private Integer contactId; // may be null
private String displayName;
public Contact() {}
public Contact(Integer contactId, String displayName) {
this.contactId = contactId;
this.displayName = displayName;
}
protected Contact(Parcel in) {
// Read nullability flag, then the value
boolean hasId = in.readByte() != 0;
this.contactId = hasId ? in.readInt() : null;
this.displayName = in.readString();
}
public Integer getContactId() { return contactId; }
public void setContactId(Integer contactId) { this.contactId = contactId; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
@Override public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags) {
// Encode nullability explicitly for boxed types
if (contactId == null) {
dest.writeByte((byte) 0);
} else {
dest.writeByte((byte) 1);
dest.writeInt(contactId);
}
dest.writeString(displayName);
}
public static final Creator<Contact> CREATOR = new Creator<Contact>() {
@Override public Contact createFromParcel(Parcel in) {
return new Contact(in);
}
@Override public Contact[] newArray(int size) {
return new Contact[size];
}
};
}
Example 2: Parcelable containing primitives, String, and a list of other Parcelables
package example.transport;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
public class ParcelBundle implements Parcelable {
private ArrayList<Row> rows;
private int count;
private String label;
public ParcelBundle() {
this.rows = new ArrayList<>();
}
protected ParcelBundle(Parcel in) {
this.count = in.readInt();
this.label = in.readString();
this.rows = in.createTypedArrayList(Row.CREATOR);
}
public ArrayList<Row> getRows() { return rows; }
public void setRows(ArrayList<Row> rows) { this.rows = rows; }
public int getCount() { return count; }
public void setCount(int count) { this.count = count; }
public String getLabel() { return label; }
public void setLabel(String label) { this.label = label; }
@Override public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(count);
dest.writeString(label);
dest.writeTypedList(rows);
}
public static final Creator<ParcelBundle> CREATOR = new Creator<ParcelBundle>() {
@Override public ParcelBundle createFromParcel(Parcel in) { return new ParcelBundle(in); }
@Override public ParcelBundle[] newArray(int size) { return new ParcelBundle[size]; }
};
// Example row element; must implement Parcelable to be used in writeTypedList
public static class Row implements Parcelable {
private long id;
private String value;
public Row(long id, String value) { this.id = id; this.value = value; }
protected Row(Parcel in) { this.id = in.readLong(); this.value = in.readString(); }
public long getId() { return id; }
public String getValue() { return value; }
@Override public int describeContents() { return 0; }
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(value);
}
public static final Creator<Row> CREATOR = new Creator<Row>() {
@Override public Row createFromParcel(Parcel in) { return new Row(in); }
@Override public Row[] newArray(int size) { return new Row[size]; }
};
}
}
Example 3: Parcelable with inheritance
package example.transport;
import android.os.Parcel;
import android.os.Parcelable;
public abstract class BasePacket implements Parcelable {
private int header;
protected BasePacket(int header) { this.header = header; }
protected BasePacket(Parcel in) { this.header = in.readInt(); }
public int getHeader() { return header; }
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(header);
}
}
public class DataPacket extends BasePacket {
private int payload;
public DataPacket(int header, int payload) {
super(header);
this.payload = payload;
}
private DataPacket(Parcel in) {
super(in);
this.payload = in.readInt();
}
public int getPayload() { return payload; }
@Override public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(payload);
}
public static final Creator<DataPacket> CREATOR = new Creator<DataPacket>() {
@Override public DataPacket createFromParcel(Parcel in) { return new DataPacket(in); }
@Override public DataPacket[] newArray(int size) { return new DataPacket[size]; }
};
}
Serializable in Java Serializable is a marker interface (no methods). An object graph can be serialized only if every referenced type in that graph is also Serializable.
- Pros: trivial to adopt (implements Serializable), useful for disk/network persistence and Java interop.
- Cons on Android: more allocations and slower than Parcelable for IPC/intents; versioning still requires care for long-term storage.
Sreializable example
package example.transport;
import java.io.Serializable;
public class NumbersCarrier implements Serializable {
private static final long serialVersionUID = 2L;
private Double alpha;
private Float beta;
public NumbersCarrier() {}
public Double getAlpha() { return alpha; }
public void setAlpha(Double alpha) { this.alpha = alpha; }
public Float getBeta() { return beta; }
public void setBeta(Float beta) { this.beta = beta; }
}
Passing values with Intent and Bundle
-
Primitives and strings
- Put: intent.putExtra("age", 42); intent.putExtra("title", "Engineer");
- Get: getIntent().getIntExtra("age", 0); getIntent().getStringExtra("title");
-
Parcelable objects
- Put: intent.putExtra("contact", contact);
- Get: Contact c = getIntent().getParcelableExtra("contact");
-
Parcelable lists
- Put: intent.putParcelableArrayListExtra("rows", rows);
- Get: ArrayList<ParcelBundle.Row> rows = getIntent().getParcelableArrayListExtra("rows");
-
Serializable objects
- Put: intent.putExtra("payload", serializableObject);
- Get: NumbersCarrier nc = (NumbersCarrier) getIntent().getSerializableExtra("payload");
- Note: every nested field must also implement Serializabel, or you will get java.io.NotSerializableException at runtime.
-
Mixed graphs
- A Serializable graph cannot contain non-Serializable members (e.g., Parcelable-only types) without causing NotSerializableException.
- Parcelable graphs must consist of parcelable-friendly members; use write/read methods for framework types.
-
Enums
- Enums are Serializable by default: intent.putExtra("state", MyState.ACTIVE);
- Alternatively, pass by name or ordinal too decouple from serialization: intent.putExtra("stateName", state.name());
Caveats and best practices
- Always read from Parcel in the same order you wrote.
- Prefer createTypedArrayList/ writeTypedList for lists of Parcelables.
- Avoid using Parcelable for long-term storage or across app version boundaries; it is not stable for persistence.
- Keep payloads small to respect Binder transaction limits.
- For large or structured persistence, prefer stable formats (e.g., JSON, Protocol Buffers) or databases, and only use Parcelable for in-process IPC and short-lived transfers.