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

  1. Confirm the selected file is the actual FreeOTP serialized token file (starts with bytes AC ED 00 05).
  2. Re-export the tokens from FreeOTP and retry the import.
  3. Do not convert, edit, or re-save the export file before importing.
  4. 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

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


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)