Skip to content

search/query returns 500 for a malformed user query; a client input error should be 400 #31878

Description

@harsh-vador

Affected module

Backend / Search

Describe the bug

A malformed search term typed by a user makes GET /api/v1/search/query return 500. Nothing on the server failed — OpenSearch correctly rejected an unparseable query — so this is a client input error reported as a server fault. It should be 400.

Reproducible from the UI with no special setup: type a AND OR b into any search box that reaches /search/query (found via the domain selector, see #31822). escapeESReservedCharacters on the UI side escapes reserved characters, not boolean keywords, so AND OR arrives at OpenSearch as an operator sequence.

curl -sG http://localhost:8585/api/v1/search/query \
  --data-urlencode 'q=*a AND OR b*' \
  -d index=domain_search_index -d from=0 -d size=15 \
  -H "Authorization: Bearer $TOKEN" -w '\n%{http_code}\n'
{"code":500,"message":"Search failed due to [es/search] failed: [search_phase_execution_exception] all shards failed | Root cause: [query_shard_exception: Failed to parse query [*a AND OR b*]]"}

Measured status codes

Against a local 1.13 stack, every /search/query failure mode returns 500 — the endpoint appears unable to return 400:

Request Status Cause
q=*a AND OR b* 500 query_shard_exception: Failed to parse queryclient input
q=*a~~b*, q=*a^^b* 500 same, unparseable operators — client input
q=* with from=9995 500 illegal_argument_exception: Result window is too largeclient input
index=nope_search_index 500 Failed to find indexclient input

For contrast, sibling endpoints classify correctly — /api/v1/domains/hierarchy?fields=parent,bogus returns 400 Invalid field name bogus.

Why this matters beyond the status code

  1. Alerting noise. Every malformed search increments the 5xx rate. Anything paging on server-error rate or an availability SLO fires on what is a user typo.
  2. It hides real outages. A genuine cluster problem and a user typing AND OR are indistinguishable in metrics and logs.
  3. Clients can't classify. A UI cannot tell "your query is malformed, fix it" from "the search backend is down, retry later", so it can't offer the right recovery. This came up directly in review on Fixes #31822: Surface domain tree search failures instead of rejecting unhandled #31823: a reviewer asked to show the server's message only for 4xx and stay generic for 5xx, which is reasonable — except the one message a user can act on is currently a 500.
  4. Shared surface. /search/query backs Explore, the global search bar, and every entity picker, so this is not specific to one dropdown.

Expected behavior

An OpenSearch/Elasticsearch rejection caused by the request itself — query parse failures, an out-of-range result window, an unknown index — returns 400 with a message the client can show. Reserve 500 for genuine backend failures: cluster unreachable, timeouts, node failures.

Where the mapping lives

SearchException already supports a status — the single-argument constructor is what pins everything to 500:

// openmetadata-spec/src/main/java/org/openmetadata/sdk/exception/SearchException.java
public SearchException(String message) {
  super(Response.Status.INTERNAL_SERVER_ERROR, ERROR_TYPE, message);   // always 500
}

private SearchException(Response.Status status, String message) { ... } // exists, unused for this path

Both search managers funnel every OpenSearch/ES error through one builder, so the classification has a single natural home in each:

  • openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchSearchManager.javabuildSearchException(OpenSearchException), ~line 1588
  • openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchSearchManager.java — the mirrored builder, ~line 1409

Both already unwrap error().rootCause() to build the message, so the root-cause type() needed for the decision is in hand. Suggested classification by root-cause type: query_shard_exception / parsing_exception / illegal_argument_exception / index_not_found_exception → 400; everything else → 500. Whatever the final list, it must be applied to both managers — they are kept in sync.

Suggested follow-up (separate, UI side)

Escaping or rejecting boolean keywords in escapeESReservedCharacters would stop the most common malformed query from ever leaving the browser. That is defence in depth, not the fix — any API client can still send one.

Version

  • OpenMetadata: 1.13 / main

Found while fixing #31822 (UI: a failed domain search was silently rendered as an empty result). That PR handles the client side; this issue is the server-side classification.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions