beemdevelopment/Aegis · error · ParseException
Not a serialized Java Object
Error message
Not a serialized Java Object
What it means
FreeOtpImporter.parse manually decodes a Java-serialized stream with a DataInputStream instead of ObjectInputStream. It first reads the stream's magic number (0xACED) and version and throws this ParseException when either does not match, i.e. the data is not a Java serialization stream at all. This is a fast-fail guard against feeding the parser a wrong or corrupt file.
Solutions
- Confirm the selected file is the actual FreeOTP serialized token file (starts with bytes AC ED 00 05).
- Re-export the tokens from FreeOTP and retry the import.
- Do not convert, edit, or re-save the export file before importing.
- Update Aegis and FreeOTP to latest versions in case the stream version constant changed.
Example fix
// before: wrong file given to the importer
byte[] data = readBytes("freeotp-backup.json"); // not a Java-serialized stream
// after: verify the magic header before importing
byte[] data = readBytes("freeotp-backup.bin");
if (!(data[0] == (byte) 0xAC && data[1] == (byte) 0xED)) {
throw new ParseException("File is not a Java-serialized FreeOTP export");
} Defensive patterns
Strategy: validation
Validate before calling
static void validateJavaSerializationHeader(byte[] data) {
if (data == null || data.length < 4) throw new ParseException("File too short");
int magic = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
if (magic != 0xACED) throw new ParseException("Not a Java serialization stream");
} Type guard
static boolean isJavaSerializedStream(byte[] data) {
return data != null && data.length >= 4
&& data[0] == (byte) 0xAC && data[1] == (byte) 0xED;
} Try / catch
try {
importer.parse(stream);
} catch (ParseException e) {
if (e.getMessage().contains("Not a serialized Java Object")) {
// file is not a Java-serialized export; prompt user to pick the correct file
showWrongFileError();
}
} Prevention
- Check the first bytes (AC ED 00 05) of the file before attempting import.
- Never convert FreeOTP exports to text/JSON formats before importing.
- Keep the original export file; re-export from FreeOTP if in doubt.
- Distinguish FreeOTP's token store file from its other serialized files by size/content.
When it happens
Trigger: Calling parse() on a file whose first 4 bytes are not the Java serialization magic 0xAC ED followed by the expected version — for example an plain-text/JSON export, a ZIP, or a truncated file.
Common situations: User selects the wrong file during FreeOTP import (e.g. the app's preferences XML or a database file instead of the serialized tokens file), or the export was converted/re-saved by another tool that changed the format.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Expected an object, found:
- Expected a class desc, found:
- Unexpected class name
- Unable to find nonce in parameters
- Unable to decode stream to bitmap
AI-assisted analysis of beemdevelopment/Aegis@d6f4e5925a (2026-09-08).
Data as JSON: /api/errors/e5ff2e3606582cd4.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java:378
private static final byte TC_NULL = 0x70;
private static final byte TC_CLASSDESC = 0x72;
private static final byte TC_OBJECT = 0x73;
private static final byte TC_STRING = 0x74;
private SerializedHashMapParser() {
}
public static Map<String, String> parse(DataInputStream inStream)
throws IOException, ParseException {
Map<String, String> map = new HashMap<>();
// Read/validate the magic number and version
int magic = inStream.readUnsignedShort();
int version = inStream.readUnsignedShort();
if (magic != MAGIC || version != VERSION) {
throw new ParseException("Not a serialized Java Object");
}
// Read the class descriptor info for HashMap
byte b = inStream.readByte();
if (b != TC_OBJECT) {
throw new ParseException("Expected an object, found: " + b);
}
b = inStream.readByte();
if (b != TC_CLASSDESC) {
throw new ParseException("Expected a class desc, found: " + b);
}
parseClassDescriptor(inStream);
// Not interested in the capacity of the map
inStream.readInt();
// Read the number of elements in the HashMap
int size = inStream.readInt();
View on GitHub (pinned to d6f4e5925a)