Google Custom Search API in Java
The call that still answers.
The Custom Search JSON API is closed to new customers, and Google ends it on January 1, 2027. A Java service that calls googleapis.com/customsearch/v1 gets no new key and no answer after that date.
SpecStream answers that request at https://api.specstream.ai/customsearch/v1 and returns Google's JSON. In Java the change is the base URI and the key. items, title, link and snippet parse as they did, and any cx is accepted.
A search of up to 10 results costs $0.005, the $5 per 1,000 Google charges, with no 10,000 a day cap. SpecStream scores 97.9 F-score on all 1,000 SimpleQA Verified questions; see the benchmarks.
New accounts get $10 of credit, no card. It covers 2,000 searches.
Custom Search from Java: SpecStream, Google and Vertex AI Search
| SpecStream | Google Custom Search JSON API | Vertex AI Search | |
|---|---|---|---|
| The request Java sends | GET https://api.specstream.ai/customsearch/v1 with key, q, num and start. Any cx is accepted | GET https://www.googleapis.com/customsearch/v1 with q, an API key and cx, the ID of the Programmable Search Engine you configured | POST https://discoveryengine.googleapis.com/v1/{servingConfig}:search, a different request and reply |
| Open to new customers | Yes. Create a key in a minute and start with $10 of credit, no card | No. Closed to new customers; discontinued on January 1, 2027 | Yes, with a free trial of 10,000 queries per account a month |
| Search scope | The whole live web at the time of the call. siteSearch limits a search to one site | Only the Programmable Search Engine you configured | Up to 50 domains. Full web search means contacting Google |
| Price | $5 per 1,000 searches of up to 10 results. No daily cap | $5 per 1,000 queries after 100 free queries a day, up to 10,000 a day, for existing customers | $1.50 per 1,000 queries on Search Standard Edition, $4.00 on Search Enterprise Edition |
| What the JSON holds | Google's shape: items with title, link, snippet, displayLink, htmlTitle, htmlSnippet and formattedUrl, searchInformation.totalResults and queries.nextPage | An items array carrying the URL, title and text snippets of each result | A different reply. Generated answers come with Search Enterprise Edition at $4.00 per 1,000 queries |
| Results a call | num from 1 to 10 and start to page, as on Google | 1 to 10 results a call, never more than 100 for a query | Billed per query: any API call to Enterprise Search counts |
| When a call fails | A busy call answers 429 with Retry-After in Google's error form and is not charged | Its terms say you may be charged for each successful request | Any API call counts as a request, direct or through an integration or the console |
Why Java code that called Custom Search moves to SpecStream
The search with java.net.http.HttpClient
The request uses the JDK's own HttpClient (Java 11 and newer); Jackson parses the reply. When a search finds nothing, Google and SpecStream both leave out items, and path("items") then iterates over nothing.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class CustomSearch {
public static void main(String[] args) throws Exception {
String base = "https://api.specstream.ai"; // was https://www.googleapis.com
String q = URLEncoder.encode("open source vector databases", StandardCharsets.UTF_8);
URI uri = URI.create(base + "/customsearch/v1?key=" + System.getenv("SPECSTREAM_API_KEY")
+ "&cx=any&num=10&q=" + q);
HttpClient http = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(30)).build();
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode data = new ObjectMapper().readTree(response.body());
if (response.statusCode() != 200) {
throw new IllegalStateException(data.path("error").path("message").asText());
}
System.out.println(data.path("searchInformation").path("totalResults").asText() + " results");
for (JsonNode item : data.path("items")) {
System.out.println(item.path("title").asText());
System.out.println(" " + item.path("link").asText());
System.out.println(" " + item.path("snippet").asText());
}
}
}Errors keep Google's object, error.code, error.message, error.errors and error.status, so the exception path in your service reads the same fields.
If the code uses google-api-services-customsearch
Google's generated Java client sets its address on the builder. In its source, CustomSearchAPI.java declares DEFAULT_ROOT_URL = "https://customsearch.googleapis.com/" and the builder overrides public Builder setRootUrl(String rootUrl). The same call before and after, with that one option and the key changed:
// Before: Google
CustomSearchAPI service = new CustomSearchAPI.Builder(
new NetHttpTransport(), GsonFactory.getDefaultInstance(), null)
.setApplicationName("search")
.build();
Search result = service.cse().list()
.setQ("open source vector databases").setCx(cx)
.setKey(System.getenv("GOOGLE_API_KEY"))
.execute();
// After: the same call, with setRootUrl on the builder
CustomSearchAPI service = new CustomSearchAPI.Builder(
new NetHttpTransport(), GsonFactory.getDefaultInstance(), null)
.setApplicationName("search")
.setRootUrl("https://api.specstream.ai/")
.build();
Search result = service.cse().list()
.setQ("open source vector databases").setCx(cx)
.setKey(System.getenv("SPECSTREAM_API_KEY"))
.execute();Tested on November 10, 2026 with google-api-services-customsearch v1-rev20240821-2.0.0: the client's own cse().list().execute() read 10 results from https://api.specstream.ai/.
Paging and a busy reply
start is 1-based. Each reply names the next page in queries.nextPage[0].startIndex while there is one. A busy call comes back 429 with a Retry-After header, in Google's error form, and is not charged; search sleeps for that many seconds and sends it again. Every page is one search at $0.005.
static JsonNode search(HttpClient http, String q, int start) throws Exception {
URI uri = URI.create("https://api.specstream.ai/customsearch/v1?key=" + System.getenv("SPECSTREAM_API_KEY")
+ "&cx=any&num=10&start=" + start + "&q=" + URLEncoder.encode(q, StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(30)).build();
while (true) {
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 429) {
return new ObjectMapper().readTree(response.body());
}
long wait = response.headers().firstValue("Retry-After").map(Long::parseLong).orElse(1L);
Thread.sleep(Math.max(1L, wait) * 1000);
}
}
// three pages, up to 30 results
HttpClient http = HttpClient.newHttpClient();
int start = 1;
for (int page = 0; page < 3; page++) {
JsonNode data = search(http, "open source vector databases", start);
for (JsonNode item : data.path("items")) {
System.out.println(item.path("link").asText());
}
JsonNode next = data.path("queries").path("nextPage").path(0);
if (next.isMissingNode()) {
break;
}
start = next.path("startIndex").asInt();
}Parameters the route honours
Nine: q, num, start, hq, exactTerms, orTerms, excludeTerms, siteSearch and siteSearchFilter. fileType, dateRestrict, lowRange and highRange are accepted and not applied. Any cx is accepted, so the engine ID in your properties file can stay. The migration guide lists every parameter and field.
Google's rate without the 10,000 a day cap
SpecStream charges $5 per 1,000 searches of up to 10 results, the rate Google charges, and has no daily cap where Google stops at 10,000 queries. Scores: 97.9 F-score on all 1,000 SimpleQA Verified questions (978 correct), 64.0 item F1 and 43.4 row F1 on all 200 WideSearch tasks. Measured on production on November 9, 2026: a median of 0.82 s at 1 search a second and 1.07 s at 20 a second.
What is kept from a search
On a key with zero_retention set, nothing of the search stays after the reply except the charge's account, key, request ID, price and time. Turn it on when you make the key or later on the same key.
The same key reads pages and returns rows
The Extract API reads up to 20 pages or documents a call at $0.80 per 1,000 pages, with 0.908 F1 on WCXB. Deep Search takes a request in plain words and returns every match as structured rows at 5¢, 15¢ or $1 a row, with the most a run can cost quoted before it starts.
Other options for the Custom Search JSON API in Java
Vertex AI Search
Google points Custom Search users here for up to 50 domains, at $1.50 per 1,000 queries on Search Standard Edition. The call is a POST to discoveryengine.googleapis.com with its own request and reply, so the Java that parses items is rewritten. SpecStream answers the request your code already builds, on the whole web.
Grounding with Google Search in the Gemini API
Gemini searches and writes an answer grounded in what it found, at $14 per 1,000 search requests on Gemini 3 and newer after 5,000 free a month. Your service receives the model's answer. SpecStream returns items to your code at $5 per 1,000.
Brave Search API
Search on Brave's own index for $5 per 1,000 requests, with $5 in free credits every month. Brave's guide says migrating is swapping the endpoint and updating how you read the response. With SpecStream the Java that reads Google's response is kept.
How SpecStream scores
The same numbers as the benchmarks page, from the official tasks and scorers.
How to run the Java example
1. Create a key under Agents & API in the workspace. New accounts get $10 of credit, no card, enough for 2,000 searches.
2. Set the key in the environment and try the route from a shell.
export SPECSTREAM_API_KEY="your key"
curl "https://api.specstream.ai/customsearch/v1?key=$SPECSTREAM_API_KEY&cx=any&q=open+source+vector+databases&num=3"3. Add Jackson (com.fasterxml.jackson.core:jackson-databind) to the build and run CustomSearch above. In code that already calls Google, change https://www.googleapis.com or https://customsearch.googleapis.com to https://api.specstream.ai and use the SpecStream key. Leave cx in place.
4. With google-api-services-customsearch, add .setRootUrl("https://api.specstream.ai/") to the builder, as shown above.
For the same move in Python, with Google's own client, see the Python page.
When the job is every match rather than a results page, write the request in plain words. SpecStream reads it back as the conditions each row must meet and the facts to return, quotes the most it can cost, and delivers the rows once you approve.
Frequently asked questions
Something we didn't answer? Talk to a human
How do I use the Google Custom Search API in Java?
GET /customsearch/v1 with key, cx and q through java.net.http.HttpClient and read items from the JSON. Google closed the API to new customers and ends it on January 1, 2027. The same request answers at https://api.specstream.ai/customsearch/v1 with a SpecStream key.What is the customsearch Java client's setting for the address?
Builder.setRootUrl, which replaces DEFAULT_ROOT_URL, https://customsearch.googleapis.com/. Set it to https://api.specstream.ai/ and pass a SpecStream key to setKey. Tested on November 10, 2026 with google-api-services-customsearch v1-rev20240821-2.0.0: the client's own cse().list().execute() read 10 results.When does Google shut down the Custom Search JSON API?
How do I get more than 10 results in Java?
start at 11, then 21, or take queries.nextPage[0].startIndex from each reply. Every page of up to 10 results is one search at $0.005.Do I need a Programmable Search Engine ID (cx)?
cx value is accepted, so a properties file that sets one stays as it is. SpecStream searches the whole live web; siteSearch keeps a search to one site.What does the Custom Search JSON API cost?
How should Java handle a 429 from the route?
Retry-After header, wait that many seconds and send the call again. The 429 comes in Google's error form and is not charged.Sources
SpecStream wrote this page. Each figure about Google comes from Google's own pages, the client setting from the client's own source, and each other figure from that company's own page.
- Google Custom Search JSON API overviewdevelopers.google.com/custom-search/v1/overview, read Nov 9, 2026
- Google Custom Search JSON API reference, cse.listdevelopers.google.com/custom-search/v1/reference/rest/v1/cse/list, read Nov 9, 2026
- Google Custom Search JSON API, using RESTdevelopers.google.com/custom-search/v1/using_rest, read Nov 9, 2026
- Google Custom Search terms of servicedevelopers.google.com/custom-search/terms, read Nov 9, 2026
- Programmable Search Engine blog: updates to our web search productsprogrammablesearchengine.googleblog.com/2026/01/updates-to-our-web-search-products.html, read Nov 9, 2026
- Google Cloud: Agent Search pricingcloud.google.com/generative-ai-app-builder/pricing, read Nov 9, 2026
- Google Cloud: servingConfigs.search methodcloud.google.com/generative-ai-app-builder/docs/reference/rest/v1/projects.locations.collections.engines.servingConfigs/search, read Nov 9, 2026
- Gemini API: Grounding with Google Searchai.google.dev/gemini-api/docs/google-search, read Nov 9, 2026
- Brave Search APIbrave.com/search/api/, read Nov 9, 2026
- Brave, alternatives to the Google Search APIbrave.com/learn/google-api-shutdown/, read Nov 9, 2026
- google-api-services-customsearch: CustomSearchAPI.javagithub.com/googleapis/google-api-java-client-services/blob/main/clients/google-api-services-customsearch/v1/2.0.0/com/google/api/services/customsearch/v1/CustomSearchAPI.java, read Nov 10, 2026
- SpecStream benchmarksspecstream.ai/benchmarks, read Nov 10, 2026
- SpecStream pricingspecstream.ai/pricing, read Nov 10, 2026
- SpecStream for agentsspecstream.ai/agents.md, read Nov 10, 2026




