apache/pulsar · error · MetadataFormatException

Unexpected metadata type: ${className}

Error message

Unexpected metadata type: ${className}

What it means

PackageMetadataUtil.readLegacy deserializes package metadata stored in the legacy Java serialization format. After applying a restrictive ObjectInputFilter (LEGACY_FILTER), it verifies the deserialized object is actually a PackageMetadata instance; anything else (or null) means the stored bytes are not valid legacy metadata, so MetadataFormatException is thrown. The library supports legacy Java serialization only as a migration path, gated behind the packagesManagementAllowLegacyJavaSerialization flag.

Source

Thrown at pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageMetadataUtil.java:107

            return readLegacy(bytes);
        }
        throw new MetadataFormatException("Unrecognized package metadata format");
    }

    private static PackageMetadata readJson(byte[] bytes) throws MetadataFormatException {
        try {
            return JSON_READER.readValue(bytes);
        } catch (IOException e) {
            throw new MetadataFormatException("Failed to parse package metadata as JSON: " + e.getMessage());
        }
    }

    private static PackageMetadata readLegacy(byte[] bytes) throws MetadataFormatException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            ois.setObjectInputFilter(LEGACY_FILTER);
            Object o = ois.readObject();
            if (!(o instanceof PackageMetadata)) {
                throw new MetadataFormatException("Unexpected metadata type: "
                        + (o == null ? "null" : o.getClass().getName()));
            }
            if (LEGACY_READ_WARNED.compareAndSet(false, true)) {
                log.warn("Read a package metadata entry in the legacy Java serialization format. "
                        + "Re-upload packages or call updateMeta to migrate them to JSON, then disable "
                        + "packagesManagementAllowLegacyJavaSerialization.");
            }
            return (PackageMetadata) o;
        } catch (MetadataFormatException e) {
            throw e;
        } catch (Exception e) {
            throw new MetadataFormatException("Rejected legacy package metadata: " + e.getMessage());
        }
    }

    private static int indexOfFirstNonWhitespace(byte[] bytes) {
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];

View on GitHub (pinned to 820761864e)

Solutions

  1. Re-upload the package or call updateMeta to rewrite the metadata in JSON format, then disable packagesManagementAllowLegacyJavaSerialization.
  2. Check what wrote the metadata blob; ensure it was written by Pulsar's package management service.
  3. If legacy reads are needed, confirm packagesManagementAllowLegacyJavaSerialization=true and that the stored class is org.apache.pulsar.packages.management.core.common.PackageMetadata.
  4. Delete the corrupt package metadata entry and re-create the package.

Example fix

// before: reading stale serialized metadata directly
PackageMetadata meta = PackageMetadataUtil.fromBytes(storedBytes);
// after: re-upload/update metadata so it is stored as JSON
metadataService.updateMetadata(packageName, PackageMetadataUtil.toBytes(newMetadata));
Defensive patterns

Strategy: validation

Validate before calling

static boolean looksLikeJsonOrEmpty(byte[] b) {
    int i = 0;
    while (i < b.length && Character.isWhitespace(b[i])) i++;
    return i < b.length && (b[i] == '{' || b[i] == '[');
}

Type guard

if (o instanceof PackageMetadata) { PackageMetadata m = (PackageMetadata) o; }

Try / catch

try {
    return PackageMetadataUtil.fromBytes(bytes);
} catch (MetadataFormatException e) {
    log.warn("Unreadable package metadata, re-upload needed", e);
    throw new RestException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
}

Prevention

When it happens

Trigger: fromBytes() is called with bytes that are neither JSON nor a Java-serialized PackageMetadata; e.g. the stored blob was written by a different class, was corrupted/truncated, or deserializeObject yielded a different type such as a String or HashMap.

Common situations: Upgrading Pulsar from versions where metadata was Java-serialized; someone hand-edited or replaced the metadata blob in storage; a different tool wrote metadata into the package storage; deserialization filter rejected the object causing readObject to produce an unexpected type or null.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/92aadf82de0a33fe. Report an issue: GitHub.