{"record":{"id":"b06bc8a20bfea11c","repo":"apache/pulsar","slug":"domain-invoked-from-wrong-resource","errorCode":null,"errorMessage":"domain() invoked from wrong resource","messagePattern":"domain\\(\\) invoked from wrong resource","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":500,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":105,"sourceCode":"public abstract class AdminResource extends PulsarWebResource {\n\n    protected NamespaceName namespaceName;\n    protected TopicName topicName;\n\n    protected BookKeeper bookKeeper() {\n        return pulsar().getBookKeeperClient();\n    }\n\n    /**\n     * Get the domain of the topic (whether it's persistent or non-persistent).\n     */\n    protected String domain() {\n        if (uri.getPath().startsWith(\"persistent/\")) {\n            return \"persistent\";\n        } else if (uri.getPath().startsWith(\"non-persistent/\")) {\n            return \"non-persistent\";\n        } else {\n            throw new RestException(Status.INTERNAL_SERVER_ERROR, \"domain() invoked from wrong resource\");\n        }\n    }\n\n    // This is a stub method for Mockito\n    @Override\n    public void validateSuperUserAccess() {\n        super.validateSuperUserAccess();\n    }\n\n    // This is a stub method for Mockito\n    @Override\n    protected void validateAdminAccessForTenant(String tenant) {\n        super.validateAdminAccessForTenant(tenant);\n    }\n\n    // This is a stub method for Mockito\n    @Override\n    protected boolean isLeaderBroker() {","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L87-L123","documentation":"AdminResource.domain() infers the resource domain ('persistent' or 'non-persistent') from the request URI path. If the path does not start with either prefix, the resource was reached from a URL that is not a topic resource, so the broker throws RestException 500 'domain() invoked from wrong resource'. It is an internal invariant check, not a client-input error.","triggerScenarios":"Calling a topic-validation code path (e.g. an endpoint that ends up calling validateTopicName -> domain()) through an admin URL whose path does not begin with 'persistent/' or 'non-persistent/', such as invoking topic-only logic from a namespace- or tenant-scoped resource, or a mis-routed/mistyped custom endpoint.","commonSituations":"Bugs in custom admin plugins or forked REST resources that reuse AdminResource topic-validation on non-topic paths; proxy/rewrite rules that strip the 'persistent/'/'non-persistent/' prefix from the URI; calling validateTopicName outside a proper topic request context (e.g. in tests with a fake URI).","solutions":["Ensure the request path is a full topic path: /admin/v2/{domain}/{tenant}/{namespace}/{topic} with domain = persistent or non-persistent.","If implementing a custom resource, do not call validateTopicName/domain() from non-topic resources; use the appropriate namespace/tenant validators instead.","Check reverse-proxy rewrite rules that may be mangling the URL path.","If triggered by a Pulsar bug, file an issue with the full request path."],"exampleFix":"// before (custom resource)\nvalidateTopicName(property, namespace, topic); // path is /admin/v2/namespaces/..., domain() fails\n\n// after\nvalidateNamespaceName(property, namespace); // use the matching validator for the resource type","handlingStrategy":"try-catch","validationCode":"String path = uri.getPath();\nif (!path.startsWith(\"persistent/\") && !path.startsWith(\"non-persistent/\")) {\n    throw new IllegalStateException(\"Topic validation requires a persistent/non-persistent topic path\");\n}","typeGuard":"boolean isTopicResourcePath(String path) {\n    return path != null && (path.startsWith(\"persistent/\") || path.startsWith(\"non-persistent/\"));\n}","tryCatchPattern":"try {\n    validateTopicName(tenant, namespace, topic);\n} catch (RestException e) {\n    if (e.getResponse().getStatus() == 500 && e.getMessage().contains(\"domain() invoked from wrong resource\")) {\n        log.error(\"Wrong resource type for topic validation; check the request path\");\n    }\n}","preventionTips":["Use full /admin/v2/{persistent|non-persistent}/... topic paths","Match the validator (tenant/namespace/topic) to the resource type in custom resources","Audit proxy rewrite rules that could strip the domain prefix"],"tags":["admin-api","rest","internal-error","url-path"],"backgroundTag":"rest-internal-server-error","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"}