{"record":{"id":"877c5f983286247e","repo":"apache/pulsar","slug":"invalid-managedledger-name-mlname","errorCode":null,"errorMessage":"Invalid managedLedger name: ${mlName}","messagePattern":"Invalid managedLedger name: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java","lineNumber":490,"sourceCode":"            return String.format(\"%s://%s/%s/%s\", domain, tenant, namespacePortion, localName);\n        } else if (parts.size() == 5) {\n            if (\"segment\".equals(parts.get(2))) {\n                // Segment topic ML name: tenant/namespace/segment/topic/descriptor\n                tenant = parts.get(0);\n                namespacePortion = parts.get(1);\n                localName = Codec.decode(parts.get(3));\n                return String.format(\"segment://%s/%s/%s/%s\", tenant, namespacePortion, localName, parts.get(4));\n            }\n            // Legacy V1 managed ledger name: tenant/cluster/namespace/domain/topic\n            // Convert to V2 format, dropping the cluster component\n            tenant = parts.get(0);\n            // parts.get(1) is the cluster, which we drop\n            namespacePortion = parts.get(2);\n            domain = parts.get(3);\n            localName = Codec.decode(parts.get(4));\n            return String.format(\"%s://%s/%s/%s\", domain, tenant, namespacePortion, localName);\n        } else {\n            throw new IllegalArgumentException(\"Invalid managedLedger name: \" + mlName);\n        }\n    }\n\n    /**\n     * Get a string suitable for completeTopicName lookup.\n     *\n     * <p>Example:\n     *\n     * <p>persistent://tenant/namespace/completeTopicName ->\n     *   persistent/tenant/namespace/completeTopicName\n     *\n     * @return\n     */\n    public String getLookupName() {\n        return String.format(\"%s/%s/%s/%s\", domain, tenant, namespacePortion, getEncodedLocalName());\n    }\n\n    public String getSchemaName() {","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java#L472-L508","documentation":"fromPersistenceNamingEncoding converts a legacy managed-ledger storage path (the on-disk name used by BookKeeper/Pulsar metadata, with encoded segments) back into a topic URL. If the managedLedger name does not have the expected segment structure (it is not a recognized legacy or current encoding), the method throws this IllegalArgumentException because the name cannot be decoded.","triggerScenarios":"Calling TopicName.fromPersistenceNamingEncoding(mlName) with a string that does not match the expected managed-ledger naming scheme — wrong number of slash-separated parts (the code expects 5 parts for the legacy format, or the modern 3-part tenant/namespace/local form), or a corrupted/shortened path read from metadata storage.","commonSituations":"Reading entries directly from ZooKeeper/BookKeeper metadata and passing the raw ledger path; hand-building ledger names; migrating data between Pulsar versions where the naming scheme differs; passing an encoded name with missing segments.","solutions":["Pass the full managed-ledger name exactly as stored (all segments), e.g. 'tenant/cluster/namespace/domain/persistent/localName' legacy form or the current 'tenant/namespace/persistent/localName'","Verify you are not truncating the path when extracting from metadata storage","If converting a topic name to ledger form, use TopicName.getPersistenceNamingEncoding() first and round-trip it"],"exampleFix":"// before\nTopicName.fromPersistenceNamingEncoding(\"persistent/my-topic\");\n// after\nTopicName.fromPersistenceNamingEncoding(\"my-tenant/my-cluster/my-ns/persistent/my-topic\");","handlingStrategy":"validation","validationCode":"public static boolean looksLikeManagedLedgerName(String mlName) {\n    if (mlName == null) return false;\n    String[] parts = mlName.split(\"/\");\n    return parts.length == 5 // legacy: tenant/cluster/namespace/domain/localName\n        || (parts.length == 4 && (parts[2].equals(\"persistent\") || parts[2].equals(\"non-persistent\")));\n}","typeGuard":"public static boolean isDecodableLedgerName(String s) { return s != null && s.split(\"/\").length >= 4; }","tryCatchPattern":"try {\n    String topicUrl = TopicName.fromPersistenceNamingEncoding(mlName);\n} catch (IllegalArgumentException e) {\n    log.error(\"Cannot decode managed ledger name '{}': {}\", mlName, e.getMessage());\n}","preventionTips":["Round-trip through getPersistenceNamingEncoding() instead of hand-building ledger names","Do not truncate paths read from ZooKeeper/BookKeeper metadata","Account for the legacy 5-part (with cluster) vs modern 4-part scheme when migrating data"],"tags":["java","managed-ledger","parsing"],"backgroundTag":"invalid-managed-ledger-name","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}