Java ObjectStreamConstants Interface
Last modified: April 16, 2025
The java.io.ObjectStreamConstants
interface defines constants used
in Java object serialization. These constants represent stream protocol versions,
magic numbers, and type markers. They are used internally by Java's serialization
mechanism.
ObjectStreamConstants
is implemented by ObjectOutputStream
and ObjectInputStream
. The constants define the binary format for
serialized objects. Understanding these helps with custom serialization and
debugging serialization issues.
ObjectStreamConstants Interface Overview
The interface contains constants for stream header, field types, and protocol versions. These values are part of Java's serialization specification. They remain consistent across JVM implementations for compatibility.
public interface ObjectStreamConstants { final static short STREAM_MAGIC = (short)0xaced; final static short STREAM_VERSION = 5; final static byte TC_BASE = 0x70; final static byte TC_NULL = (byte)0x70; final static byte TC_REFERENCE = (byte)0x71; final static byte TC_CLASSDESC = (byte)0x72; final static byte TC_OBJECT = (byte)0x73; final static byte TC_STRING = (byte)0x74; final static byte TC_ARRAY = (byte)0x75; final static byte TC_CLASS = (byte)0x76; final static byte TC_BLOCKDATA = (byte)0x77; final static byte TC_ENDBLOCKDATA = (byte)0x78; final static byte TC_RESET = (byte)0x79; final static byte TC_BLOCKDATALONG = (byte)0x7A; final static byte TC_EXCEPTION = (byte)0x7B; final static byte TC_LONGSTRING = (byte)0x7C; final static byte TC_PROXYCLASSDESC = (byte)0x7D; final static byte TC_ENUM = (byte)0x7E; final static int baseWireHandle = 0x7e0000; final static byte SC_WRITE_METHOD = 0x01; final static byte SC_BLOCK_DATA = 0x08; final static byte SC_SERIALIZABLE = 0x02; final static byte SC_EXTERNALIZABLE = 0x04; final static byte SC_ENUM = 0x10; }
The code above shows key constants from ObjectStreamConstants
. These
include magic numbers, type codes, and serialization flags. The values are used
to identify different parts of serialized data in the stream.
Checking Stream Magic Number
The STREAM_MAGIC constant identifies Java serialization streams. This example shows how to verify a file contains serialized Java objects by checking for the magic number. The value should be 0xaced at the start of valid streams.
import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; public class MagicNumberChecker { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("serialized.data"); DataInputStream dis = new DataInputStream(fis)) { short magic = dis.readShort(); if (magic == java.io.ObjectStreamConstants.STREAM_MAGIC) { System.out.println("Valid Java serialization stream detected"); System.out.println("Stream version: " + dis.readShort()); } else { System.out.println("Not a Java serialization stream"); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } } }
This example reads the first two bytes of a file as a short value. It compares this with STREAM_MAGIC (0xaced) to verify it's a serialization stream. If valid, it then reads the stream version number. This is useful for validating serialized data files.
Identifying Serialized Object Types
The TC_* constants identify different types in serialized streams. This example demonstrates reading type markers from a serialized file. Each object in the stream is preceded by a type code byte.
import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; public class TypeMarkerReader { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("objects.ser"); DataInputStream dis = new DataInputStream(fis)) { // Skip stream header (magic + version) dis.readShort(); dis.readShort(); byte typeMarker; while ((typeMarker = dis.readByte()) != -1) { switch (typeMarker) { case java.io.ObjectStreamConstants.TC_NULL: System.out.println("TC_NULL: null reference"); break; case java.io.ObjectStreamConstants.TC_STRING: System.out.println("TC_STRING: " + dis.readUTF()); break; case java.io.ObjectStreamConstants.TC_OBJECT: System.out.println("TC_OBJECT: new object"); // Skip class description and field data dis.readInt(); // handle break; default: System.out.println("Unknown type marker: " + typeMarker); return; } } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } } }
This example reads type markers from a serialized file after skipping the header. It demonstrates handling different object types using the TC_* constants. For TC_STRING, it reads the actual string value. More complete implementations would handle all type codes.
Checking Serializable Class Flags
The SC_* constants represent class serialization characteristics. This example shows how to check if a class implements writeObject by examining its flags. The flags are stored in class descriptors in serialized streams.
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ClassFlagsChecker { static class TestClass implements Serializable { private static final long serialVersionUID = 1L; private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { // Custom serialization } } public static void main(String[] args) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(new TestClass()); oos.close(); byte[] data = baos.toByteArray(); // Find classDescFlags (after magic, version, and TC_OBJECT) int flagsOffset = 4 + 1 + 4; // Adjust based on actual stream structure byte flags = data[flagsOffset]; if ((flags & java.io.ObjectStreamConstants.SC_WRITE_METHOD) != 0) { System.out.println("Class has custom writeObject method"); } if ((flags & java.io.ObjectStreamConstants.SC_SERIALIZABLE) != 0) { System.out.println("Class implements Serializable"); } } }
This example serializes a test class and examines the serialized data. It checks for the SC_WRITE_METHOD flag which indicates custom serialization. The flags are bitmask values that can be combined (SC_SERIALIZABLE | SC_WRITE_METHOD).
Handling Block Data in Streams
The TC_BLOCKDATA and TC_BLOCKDATALONG markers indicate raw data blocks. This example demonstrates processing block data in a serialized stream. Block data typically contains primitive values or strings.
import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; public class BlockDataProcessor { public static void main(String[] args) { // Simulate serialized data with block data byte[] simulatedData = { (byte)0xac, (byte)0xed, 0x00, 0x05, // header 0x77, 0x04, 0x00, 0x00, 0x00, 0x0a, // TC_BLOCKDATA, length 4, value 10 0x78 // TC_ENDBLOCKDATA }; try (ByteArrayInputStream bais = new ByteArrayInputStream(simulatedData); DataInputStream dis = new DataInputStream(bais)) { // Skip stream header dis.readShort(); dis.readShort(); byte marker = dis.readByte(); if (marker == java.io.ObjectStreamConstants.TC_BLOCKDATA) { int length = dis.readByte() & 0xff; System.out.println("Block data found, length: " + length); int value = dis.readInt(); System.out.println("Block data value: " + value); byte endMarker = dis.readByte(); if (endMarker != java.io.ObjectStreamConstants.TC_ENDBLOCKDATA) { System.out.println("Unexpected end marker"); } } } catch (IOException e) { System.err.println("Error processing data: " + e.getMessage()); } } }
This example processes a simulated serialization stream containing block data. It reads the TC_BLOCKDATA marker followed by length and actual data. The length is unsigned (0-255). TC_BLOCKDATALONG is similar but uses 4-byte length for larger blocks.
Working with References
The TC_REFERENCE marker indicates a back reference to a previously serialized object. This example shows how references work in serialization. References help avoid duplication when the same object appears multiple times.
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ReferenceExample { static class TestObject implements Serializable { private static final long serialVersionUID = 1L; String value; TestObject(String value) { this.value = value; } } public static void main(String[] args) throws Exception { TestObject obj1 = new TestObject("First"); TestObject obj2 = obj1; // Same object reference ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj1); oos.writeObject(obj2); oos.close(); byte[] data = baos.toByteArray(); DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data)); // Skip header and first object dis.readShort(); // STREAM_MAGIC dis.readShort(); // STREAM_VERSION dis.readByte(); // TC_OBJECT dis.readInt(); // handle // Check if second object is a reference byte marker = dis.readByte(); if (marker == java.io.ObjectStreamConstants.TC_REFERENCE) { int handle = dis.readInt(); System.out.println("Second object is reference to handle: " + handle); } } }
This example serializes two references to the same object. The second write produces a TC_REFERENCE marker pointing to the first object's handle. The handle is a unique identifier assigned during serialization. This demonstrates Java's object sharing mechanism.
Source
Java ObjectStreamConstants Interface Documentation
In this article, we've covered the essential constants and their usage from the Java ObjectStreamConstants interface. Understanding these helps with advanced serialization tasks and debugging serialized streams.
Author
List all Java tutorials.