transient
The transient keyword marks an instance field as excluded from Java's default serialization mechanism. When ObjectOutputStream serializes an object, it skips all transient fields — they are not written to the stream. When ObjectInputStream deserializes the object, transient fields are set to their default values: null for object references, 0 for numeric types, false for boolean. The transient modifier is used for three distinct purposes: fields whose values cannot be meaningfully serialized (network connections, threads, file handles, native resources), fields that contain sensitive data that must not leave the JVM (passwords, session tokens, cryptographic keys), and fields that can be derived from other serialized fields (cached computed values, derived indexes, memoized results). When transient fields need to be re-populated after deserialization, the readObject() hook is used to re-derive them. This entry covers the full transient contract, each use case with its correct pattern, interaction with final fields (transient final is legal but pointless since final fields cannot be re-assigned in readObject), and the distinction between transient and serialPersistentFields.
transient Contract — Behavior and Default Values
// ── Basic transient behavior ──────────────────────────────────────────
public class ConnectionWrapper implements Serializable {
private static final long serialVersionUID = 1L;
private String serverUrl;
private int port;
private transient Socket connection; // cannot be serialized — skip it
private transient InputStream reader; // tied to connection — skip it
public ConnectionWrapper(String serverUrl, int port) throws IOException {
this.serverUrl = serverUrl;
this.port = port;
this.connection = new Socket(serverUrl, port); // establish connection
this.reader = connection.getInputStream();
}
// After deserialization: connection = null, reader = null
// Must reconnect before use — readObject handles this:
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // restores serverUrl and port
// Re-establish the connection using the restored server/port:
this.connection = new Socket(serverUrl, port);
this.reader = connection.getInputStream();
}
}
// ── Default values after deserialization ──────────────────────────────
public class TransientDemo implements Serializable {
private static final long serialVersionUID = 1L;
// These are serialized normally:
private String name = "default";
private int count = 42;
// These are transient — set to type defaults after deserialization:
private transient String computed = "COMPUTED"; // → null after deserialization
private transient int cached = 999; // → 0 after deserialization
private transient boolean ready = true; // → false after deserialization
private transient List<String> index; // → null after deserialization
}
TransientDemo obj = new TransientDemo();
System.out.println(obj.computed); // "COMPUTED" — set by field initializer
byte[] bytes = serialize(obj);
TransientDemo restored = (TransientDemo) deserialize(bytes);
System.out.println(restored.name); // "default" — serialized
System.out.println(restored.count); // 42 — serialized
System.out.println(restored.computed); // null — transient field reset to default
System.out.println(restored.cached); // 0 — transient field reset to default
System.out.println(restored.ready); // false — transient field reset to default
// ── transient volatile: both modifiers are valid on one field ──────────
public class CachedResult implements Serializable {
private static final long serialVersionUID = 1L;
private int data;
private transient volatile String cachedStr; // transient + volatile: valid and useful
// transient: not serialized
// volatile: thread-safe lazy initialization in multi-threaded context
}
// ── transient final: legal but largely useless ─────────────────────────
public class FinalTransient implements Serializable {
private static final long serialVersionUID = 1L;
private final transient int value; // transient final: excluded from serialization
// After deserialization: value = 0 (int default)
// Cannot be re-assigned in readObject() — final fields are immutable after construction
// The only way to "set" it would be via Unsafe or serialization proxy pattern
FinalTransient(int v) { this.value = v; }
}Use Cases — Sensitive Data, Derived Fields, and Non-Serializable Resources
// ── Use case 1: Sensitive data — password field ───────────────────────
public class UserCredentials implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // NEVER serialize passwords in plaintext
private transient char[] securePassword; // Or as char[] (can be zeroed out)
public UserCredentials(String username, String password) {
this.username = username;
this.password = password;
}
// After deserialization: password = null, securePassword = null
// Application must re-authenticate to get the password again
// DO NOT write password in writeObject — that would defeat the purpose
}
// Sensitive data with encrypted persistence:
public class ApiClient implements Serializable {
private static final long serialVersionUID = 1L;
private String endpoint;
private transient String apiKey; // sensitive — not in default stream
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(encryptApiKey(apiKey)); // write encrypted form only
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
String encrypted = (String) ois.readObject();
this.apiKey = decryptApiKey(encrypted); // decrypt on load
}
private String encryptApiKey(String key) { return "ENCRYPTED:" + key; }
private String decryptApiKey(String enc) { return enc.substring(10); }
}
// ── Use case 2: Derived/cached fields ────────────────────────────────
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> words; // serialized: the source of truth
private transient Map<String, Integer> wordIndex; // derived: rebuilt in readObject
private transient int cachedWordCount = -1; // cached: recomputed lazily
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // restore 'words'
this.wordIndex = buildIndex(words); // rebuild derived index
this.cachedWordCount = -1; // reset cache sentinel
}
public int wordCount() {
if (cachedWordCount == -1) cachedWordCount = words.size();
return cachedWordCount;
}
private Map<String, Integer> buildIndex(List<String> words) {
Map<String, Integer> index = new HashMap<>();
for (int i = 0; i < words.size(); i++) index.put(words.get(i), i);
return index;
}
}
// ── Use case 3: Non-serializable resource with lazy reconnect ──────────
public class DatabaseRepository implements Serializable {
private static final long serialVersionUID = 1L;
private String jdbcUrl;
private String username;
private transient Connection conn; // Connection is not Serializable
// Lazy initialization — works both initially and after deserialization:
private Connection getConnection() throws SQLException {
if (conn == null || conn.isClosed()) {
conn = DriverManager.getConnection(jdbcUrl, username, getPassword());
}
return conn;
}
public List<String> queryAll() throws SQLException {
try (PreparedStatement ps = getConnection().prepareStatement("SELECT name FROM data");
ResultSet rs = ps.executeQuery()) {
List<String> results = new ArrayList<>();
while (rs.next()) results.add(rs.getString(1));
return results;
}
}
// After deserialization: conn = null → getConnection() creates new connection lazily
}
// ── Thread-safe lazy initialization for transient volatile ────────────
public class HeavyCache implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> data;
private transient volatile Map<String, List<String>> groupIndex; // transient + volatile
public Map<String, List<String>> getIndex() {
if (groupIndex == null) {
synchronized (this) {
if (groupIndex == null) {
groupIndex = buildGroupIndex(data); // thread-safe lazy init
}
}
}
return groupIndex;
}
// Works correctly both on first use and after deserialization (where groupIndex = null)
}