beemdevelopment/Aegis · error · ParseException
Unexpected class name
Error message
Unexpected class name: %s
What it means
parseClassDescriptor reads the UTF class name from the Java-serialized class descriptor and requires it to be exactly java.util.HashMap; any other class name triggers this ParseException with the found name. The FreeOTP importer only supports the specific HashMap-at-top-level serialization layout, so unexpected classes mean the file is not the token store format it can decode.
Solutions
- Check the class name printed in the message — it tells you what the file actually contains.
- Re-export the token store from FreeOTP, making sure to pick the token backup, not another serialized file.
- Ensure the FreeOTP version matches the format Aegis's importer expects (update Aegis if FreeOTP is newer).
- Verify the class descriptor's serialVersionUID also matches after fixing the class name — the parser checks it next.
Example fix
// before: importing a wrong serialized file
File f = new File("freeotp_prefs.bin"); // serializes SharedPreferences, not HashMap
// after: select the actual token store
File f = new File("freeotp_tokens.bin"); // serializes java.util.HashMap
def parseTokenStore(File f) { parse(f); } Defensive patterns
Strategy: validation
Validate before calling
static String peekSerializedClassName(byte[] data) {
// header(4) + TC_OBJECT + TC_CLASSDESC + 2-byte length -> then UTF class name
if (data == null || data.length < 8) return null;
int nameLen = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
if (data.length < 8 + nameLen) return null;
return new String(data, 8, nameLen, StandardCharsets.UTF_8);
}
// usage: require "java.util.HashMap" before importing Type guard
static boolean isHashMapClassDescriptor(byte[] data) {
String name = peekSerializedClassName(data);
return "java.util.HashMap".equals(name);
} Try / catch
try {
importer.parse(stream);
} catch (ParseException e) {
if (e.getMessage().startsWith("Unexpected class name:")) {
String actual = e.getMessage().substring(e.getMessage().indexOf(':') + 2);
throw new ImportException("File contains " + actual + " instead of a FreeOTP HashMap token store", e);
}
} Prevention
- Decode the class name from the class descriptor before running the importer and require java.util.HashMap.
- Verify you are importing FreeOTP's token backup, not another Java-serialized file (prefs, settings).
- Also pre-check serialVersionUID 2768647097183612297 (HashMap's) to catch version drift early.
- Keep exporter and importer app versions in sync with the supported format.
When it happens
Trigger: A Java serialization stream whose class descriptor names any class other than java.util.HashMap — e.g. java.util.Hashtable, a custom FreeOTP token class, or a serialized object from a different app placed in the same file.
Common situations: Importing an export from a different authenticator app, a FreeOTP version that changed its storage layout, or mixing up files (settings store vs token store) both of which are Java-serialized.
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
- Not a serialized Java Object
- Expected an object, found:
- Expected a class desc, found:
- 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/98568fe77d085202.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java:412
// 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);
}
return map;
}
private static void parseClassDescriptor(DataInputStream inputStream)
throws IOException, ParseException {
// Check whether we're dealing with a HashMap and a version we support
String className = parseUTF(inputStream);
if (!className.equals(HashMap.class.getName())) {
throw new ParseException(String.format("Unexpected class name: %s", className));
}
long serialVersionUID = inputStream.readLong();
if (serialVersionUID != SERIAL_VERSION_UID) {
throw new ParseException(String.format("Unexpected serial version UID: %d", serialVersionUID));
}
// Read past all of the fields in the class
byte fieldDescriptor = inputStream.readByte();
if (fieldDescriptor == TC_NULL) {
return;
}
int totalFieldSkip = 0;
int fieldCount = inputStream.readUnsignedShort();
for (int i = 0; i < fieldCount; i++) {
char fieldType = (char) inputStream.readByte();
parseUTF(inputStream);
switch (fieldType) {
case 'F': // float (4 bytes)View on GitHub (pinned to d6f4e5925a)