apache/pulsar · warning · RestException

property filter must be in the form key=value, got: ${entry}

Error message

property filter must be in the form key=value, got: ${entry}

What it means

ScalableTopics.parseKeyValuePairs parses property-filter query entries as key=value. An entry without '=' at all, an empty key ('=v'), or an empty value ('k=') is rejected with HTTP 412. The message interpolates the offending entry.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/ScalableTopics.java:148

    }

    /**
     * Parse {@code key=value} entries from a list of query parameter values into a map.
     * Accepts {@code null} / empty input. Rejects malformed entries (no {@code =}, empty
     * key, or empty value) with a 412.
     */
    private static Map<String, String> parseKeyValuePairs(List<String> entries) {
        if (entries == null || entries.isEmpty()) {
            return Map.of();
        }
        Map<String, String> result = new java.util.LinkedHashMap<>(entries.size());
        for (String entry : entries) {
            if (entry == null || entry.isEmpty()) {
                continue;
            }
            int eq = entry.indexOf('=');
            if (eq <= 0 || eq == entry.length() - 1) {
                throw new RestException(Response.Status.fromStatusCode(412),
                        "property filter must be in the form key=value, got: " + entry);
            }
            result.put(entry.substring(0, eq), entry.substring(eq + 1));
        }
        return result;
    }

    // --- Create ---

    @PUT
    @Path("/{tenant}/{namespace}/{topic}")
    @Operation(summary = "Create a new scalable topic.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "204", description = "Scalable topic created successfully"),
            @ApiResponse(responseCode = "401",
                    description = "Don't have permission to administrate resources on this tenant"),
            @ApiResponse(responseCode = "403", description = "Don't have admin permission on the namespace"),
            @ApiResponse(responseCode = "409", description = "Scalable topic already exists"),

View on GitHub (pinned to 820761864e)

Solutions

  1. Provide each filter as strict key=value with non-empty key and non-empty value
  2. Check shell quoting so '=' survives (quote the whole URL/param)
  3. Drop empty entries — they are skipped, but malformed non-empty entries throw

Example fix

// before
?properties=env
// after
?properties=env=prod
Defensive patterns

Strategy: validation

Validate before calling

for (String e : filters) { if (e != null && !e.isEmpty() && !(e.contains("=") && e.indexOf('=') > 0 && e.indexOf('=') < e.length()-1)) throw new IllegalArgumentException("bad filter: " + e); }

Type guard

boolean isValidFilter(String e) { int i = e == null ? -1 : e.indexOf('='); return i > 0 && i < e.length() - 1; }

Try / catch

try { admin.topics().list(namespace, filters); } catch (PulsarAdminException e) { if (e.getStatusCode() == 412) { /* fix key=value filters */ } }

Prevention

When it happens

Trigger: Endpoints using propertyFilters (e.g. listing topics with property filters) with query params like ?properties=key or ?properties==value or ?properties=key=.

Common situations: Hand-built URLs missing the value after '='; shell quoting stripping parts of the filter; passing multiple values where only key=value pairs are allowed.

Related errors


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