apache/pulsar · error · MetadataFormatException
Rejected legacy package metadata:
Error message
Rejected legacy package metadata:
What it means
This is the catch-all wrapper in readLegacy: any exception thrown while reading legacy Java-serialized metadata (InvalidClassException, ClassNotFoundException, StreamCorruptedException, EOFException, etc.) is rethrown as MetadataFormatException with the original message appended. It signals the stored bytes could not be safely deserialized as legacy package metadata.
Source
Thrown at pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageMetadataUtil.java:119
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];
if (b != ' ' && b != '\t' && b != '\n' && b != '\r') {
return i;
}
}
return -1;
}
// Source-compatible overloads for callers (including external integrations) that haven't
// been updated to the explicit-flag form. Defaults track the production defaults.
@Deprecated
public static byte[] toBytes(PackageMetadata packageMetadata) {
return toBytes(packageMetadata, true);View on GitHub (pinned to 820761864e)
Solutions
- Re-upload the package or call updateMeta to migrate the entry to JSON format, then disable packagesManagementAllowLegacyJavaSerialization.
- Inspect the wrapped message in the MetadataFormatException to identify the underlying cause (class-not-found vs corrupted stream vs filter rejection).
- If the class shape changed across versions, migrate metadata using the same Pulsar version that wrote it.
- Delete and recreate the affected package metadata entry.
Example fix
// before: relying on legacy serialization conf.setPackagesManagementAllowLegacyJavaSerialization(true); // after: migrate and disable legacy support metadataService.updateMetadata(packageName, PackageMetadataUtil.toBytes(newMetadata)); conf.setPackagesManagementAllowLegacyJavaSerialization(false);
Defensive patterns
Strategy: try-catch
Try / catch
try {
PackageMetadata m = PackageMetadataUtil.fromBytes(bytes);
} catch (MetadataFormatException e) {
Throwable cause = e.getCause();
log.warn("Legacy metadata rejected: {} (cause: {})", e.getMessage(), cause, e);
// treat as corrupt entry: re-upload or delete
} Prevention
- Migrate legacy entries with updateMeta before upgrading Pulsar versions.
- Avoid mixing storage dirs between Pulsar versions without migration.
- Keep the serialized class shape stable or always rewrite metadata in JSON.
- Monitor for MetadataFormatException from read paths.
When it happens
Trigger: fromBytes() is given bytes in the legacy format that fail deserialization: stream corrupted, class not found on classpath, class version mismatch, or the ObjectInputFilter rejected a field/class (e.g. a nested non-whitelisted object inside PackageMetadata).
Common situations: Pulsar upgrade changed the PackageMetadata class shape (serialVersionUID mismatch); metadata blob truncated or corrupted on disk/storage backend; security filter (LEGACY_FILTER) rejects classes referenced in the serialized graph.
Related errors
- Unexpected metadata type: ${className}
- Failed to deserialize LongBitmap
- ${key} already exists in the dynamicConfigurationMap
- Topic factory failed to create topic
- No more range can assigned to new consumer, assigned consume
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/e47d2b67aded3c95.
Report an issue: GitHub.