SerpApi reports some failures inside the body of an HTTP 200 response,
for example the google_events engine:
HTTP 200
{"search_metadata":{"status":"Success"},
"search_information":{"events_results_state":"Fully empty"},
"error":"Google hasn't returned any results for this query."}
The client decided success purely from the status code, so search()
returned an object that was semantically an error. Callers then reached
for the key they expected and got a NullPointerException pointing at
their own code, with the explanation sitting unread in the error field.
This is what broke GoogleEventsTest.
Check for a body-level error in json() and location(), routing it
through the existing triggerSerpApiException so every SerpApi error
reaches the caller as a SerpApiException regardless of status code.
html() still returns its raw String unchecked; parsing arbitrary HTML
as JSON to look for an error field is not worth the risk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Problem
SerpApi reports some failures inside the body of an HTTP 200 response. The google_events engine is currently doing exactly this:
{ "search_metadata": { "status": "Success" }, "search_information": { "events_results_state": "Fully empty" }, "error": "Google hasn't returned any results for this query." }HTTP 200, status: "Success", and an error field.
SerpApiHttp.get() decided success purely from the status code, so triggerSerpApiException was never reached and search() returned an object that is semantically an error. The caller then did the natural thing:
getAsJsonArray returns null for an absent key, so this NPEs — pointing at the caller's own line, with the actual explanation sitting unread in the error field. This is what broke GoogleEventsTest in CI, and every consumer of this library hits the same edge.
Change
Check for a body-level error in json() and location(), routing it through the existing triggerSerpApiException so a SerpApi error always reaches the caller as a SerpApiException, regardless of status code.
location() needs its own check because it bypasses json() and parses an array — an error body is an object, so it previously died on the cast instead.
Tests
New ErrorResponseTest stubs the HTTP client, so it runs offline and needs no SERPAPI_KEY. Verified non-vacuous: with the SerpApi.java change stashed, the three error-path tests fail and the two happy-path tests still pass.
Notes for review
🤖 Generated with Claude Code