beemdevelopment/Aegis · error · ParseException

Expected a string object, found

Error message

Expected a string object, found: %d

What it means

SerializedHashMapParser.parseStringObject reads the next byte of the serialized FreeOTP stream expecting a TC_STRING marker (a Java serialized string object). Any other object type code means the parser's expectations about the HashMap's contents no longer line up with the actual stream, so it throws this ParseException.

Solutions

  1. Re-export tokens from the stock FreeOTP app and retry the import.
  2. Verify the file is a FreeOTP backup (serialized HashMap of String->String) rather than another serialized object.
  3. Check whether earlier skip logic (field descriptors, trailing 4 bytes) misaligned the stream for this producer version and adjust skipBytes accordingly.
  4. Dump the serialized stream to inspect object graph and confirm where the non-string object appears.

Example fix

// before
byte objectType = inputStream.readByte();
if (objectType != TC_STRING) {
    throw new ParseException(String.format("Expected a string object, found: %d", objectType));
}
// after: tolerate known non-string markers where appropriate
byte objectType = inputStream.readByte();
if (objectType == TC_NULL) { return null; }
if (objectType != TC_STRING) {
    throw new ParseException(String.format("Expected a string object, found: %d", objectType));
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    importer.importFromUri(uri);
} catch (DatabaseImporterException e) {
    if (e.getMessage() != null && e.getMessage().contains("Expected a string object")) {
        showError("The backup layout differs from what the importer expects; re-export from stock FreeOTP.");
    }
}

Prevention

When it happens

Trigger: During parse(), when consuming keys/values of the serialized HashMap, a byte that is not TC_STRING appears where a string object is expected — e.g. the map holds non-String values, the offset is misaligned due to earlier skipped bytes, or the file isn't a FreeOTP backup.

Common situations: Backups from modified FreeOTP versions storing extra object types; importing a different app's Java-serialized HashMap; a parsing offset bug after the class-descriptor skip produced desynchronized reads.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of beemdevelopment/Aegis@d6f4e5925a (2026-09-08). Data as JSON: /api/errors/8956752099322243. Report an issue: GitHub.

Appendix: source

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

                    case 'F': // float (4 bytes)
                    case 'I': // int (4 bytes)
                        totalFieldSkip += 4;
                        break;
                    default:
                        throw new ParseException(String.format("Unexpected field type: %s", fieldType));
                }
            }
            inputStream.skipBytes(totalFieldSkip);

            // Not sure what these bytes are, just skip them
            inputStream.skipBytes(4);
        }

        private static String parseStringObject(DataInputStream inputStream)
                throws IOException, ParseException {
            byte objectType = inputStream.readByte();
            if (objectType != TC_STRING) {
                throw new ParseException(String.format("Expected a string object, found: %d", objectType));
            }

            int length = inputStream.readUnsignedShort();
            byte[] strBytes = new byte[length];
            inputStream.readFully(strBytes);

            return new String(strBytes, StandardCharsets.UTF_8);
        }

        private static String parseUTF(DataInputStream inputStream) throws IOException {
            int length = inputStream.readUnsignedShort();
            byte[] strBytes = new byte[length];
            inputStream.readFully(strBytes);
            return new String(strBytes, StandardCharsets.UTF_8);
        }

        private static class ParseException extends Exception {
            public ParseException(String message) {

View on GitHub (pinned to d6f4e5925a)