beemdevelopment/Aegis · error · ParseException

Unexpected field type

Error message

Unexpected field type: %s

What it means

While skipping the field descriptors of the serialized HashMap class, parseClassDescriptor recognizes only primitive field type codes it knows ('F' float, 'I' int, plus other handled cases above the shown snippet). An unknown Java serialization field type code means the class descriptor layout differs from what FreeOTP is expected to write, so parsing aborts.

Solutions

  1. Re-export the tokens using the stock FreeOTP app so the class descriptor matches the expected layout.
  2. Confirm the file is a genuine FreeOTP backup and not another app's serialized data.
  3. Extend the switch in parseClassDescriptor to handle the additional field type codes (e.g. 'J' long, 'D' double, 'Z' boolean) with correct byte widths.
  4. Inspect the stream with a Java serialization dumper to identify the unexpected field type.

Example fix

// before
default:
    throw new ParseException(String.format("Unexpected field type: %s", fieldType));
// after
case 'J': // long (8 bytes)
    totalFieldSkip += 8;
    break;
case 'D': // double (8 bytes)
    totalFieldSkip += 8;
    break;
default:
    throw new ParseException(String.format("Unexpected field type: %s", fieldType));
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the source is stock FreeOTP before import
boolean isKnownSource = backupMetadata.getString("producer", "").equals("FreeOTP");
if (!isKnownSource) throw new IllegalArgumentException("Unsupported backup producer");

Try / catch

try {
    importer.importFromUri(uri);
} catch (DatabaseImporterException e) {
    if (e.getMessage() != null && e.getMessage().contains("Unexpected field type")) {
        showError("Backup contains fields this importer doesn't support; update Aegis or re-export from stock FreeOTP.");
    }
}

Prevention

When it happens

Trigger: A FreeOTP backup whose serialized HashMap class declares a field with a type code the skip-logic doesn't handle (e.g. arrays, objects, doubles, longs not covered by the switch), indicating a different producing app/version.

Common situations: Importing backups from FreeOTP forks that added fields to the stored map; importing a non-FreeOTP Java-serialized file; a corrupted stream where the field descriptor byte is garbage.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

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

            }

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

View on GitHub (pinned to d6f4e5925a)