beemdevelopment/Aegis · error · ParseException

Expected an object, found:

Error message

Expected an object, found: 

What it means

In FreeOtpImporter.parse, after validating the serialization magic and version, the parser expects the next byte to be TC_OBJECT (0x73), the marker that a Java-serialized object follows. Any other value means the stream deviates from the expected Java serialization layout, so a ParseException is thrown with the offending byte appended to the message.

Solutions

  1. Check the reported byte value in the message against Java serialization tag constants to identify what the stream actually contains.
  2. Re-export the FreeOTP data and verify the file is byte-identical (compare hashes) to a known-good export.
  3. Strip any wrapper/extra bytes so the stream begins exactly at the AC ED header.
  4. Update FreeOTP/Aegis in case the format version diverged.

Example fix

// before: parsing a stream with junk bytes before the object tag
parse(new DataInputStream(new FileInputStream(f))); // offset by 1 -> b != TC_OBJECT
// after: locate the real stream start first
byte[] data = readAllBytes(f);
int start = indexOfJavaMagic(data); // index of 0xAC 0xED
parse(new DataInputStream(new ByteArrayInputStream(data, start, data.length - start)));
Defensive patterns

Strategy: validation

Validate before calling

static void validateStreamTags(byte[] data) {
    // after the 4-byte header the stream must carry TC_OBJECT (0x73)
    if (data.length >= 5 && data[4] != 0x73) {
        throw new ParseException(String.format("Expected TC_OBJECT, found 0x%02X at offset 4", data[4]));
    }
}

Type guard

static boolean startsWithObjectTag(byte[] data) {
    return data != null && data.length >= 5 && data[4] == 0x73;
}

Try / catch

try {
    importer.parse(stream);
} catch (ParseException e) {
    if (e.getMessage().startsWith("Expected an object, found:")) {
        logStreamBytes(stream); // dump the first bytes to find embedded wrappers
        throw new ImportException("FreeOTP stream has unexpected top-level tag", e);
    }
}

Prevention

When it happens

Trigger: Parsing a stream whose 5th byte is not 0x73 — e.g. the file embeds a serialization stream with leading/trailing extra bytes, contains a different top-level tag (TC_NULL, TC_BLOCKDATA), or is corrupt after the header.

Common situations: FreeOTP exports that were concatenated with extra data, streams produced by a newer/older FreeOTP that serializes a different top-level structure, or files damaged during transfer (FTP ASCII mode, partial downloads).

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/6e2d29a6a31a233a. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java:384

        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();

            // Parse each key-value pair in the map
            for (int i = 0; i < size; i++) {
                String key = parseStringObject(inStream);
                String value = parseStringObject(inStream);
                map.put(key, value);
            }

View on GitHub (pinned to d6f4e5925a)