Workaround for infinite loop bug in Google Ads API
Shahar Gotshtat, Thu Jun 24 2021, 2 min
About a week before June 23rd, 2021, we got this kind message:
Important: Upgrade from Google Ads API v4 and v5 by June 23, 2021
Dear Google Ads API Developer (Manager Account ID XXX),
Google Ads API v4 and v5 have been deprecated and will stop accepting requests starting on June 23, 2021.
Your developer token has recently submitted requests to v4 or v5. Migrate to the latest version as soon as possible to continue accessing the Google Ads API. Use the migration guide to upgrade.
If you have any questions, please don't hesitate to contact us at
The Google Ads API Team
We immediately started the upgrade to the latest API version (v8), and it went pretty smoothly - until we tried to fetch AdsCustomer with a token that didn’t have the right permissions (which happens a lot).
Our code, copied from this example, works well when the request has a valid response. However, when the request has an error, such as "permission denied", we never get an answer. It seems the callable is stuck on an infinite loop. A short Google search led us to this open bug and a few other similar ones, but we couldn’t find a solution.
In the end, we solved the issue with a workaround that wraps the problematic piece of code with a thread with a timeout - so if we don’t get a response in 5 seconds, we return an empty list.
Here is our code sample:
private Stream<AdsCustomer> fetchAdsCustomer(String customerId, String refreshToken) {
    GoogleAdsClient googleAdsClient = getClient(customerId, refreshToken);

    try (GoogleAdsServiceClient googleAdsServiceClient =
                 googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
        String query =
                "SELECT, customer_client.descriptive_name, "
                        + "customer_client.currency_code, customer_client.time_zone, "
                        + "customer_client.manager "
                        + "FROM customer_client "
                        + "WHERE customer_client.level <= 1";

        SearchGoogleAdsStreamRequest request =

        return getAdsCustomerFromStream(googleAdsServiceClient, request, customerId).stream();
    }  catch (Exception e) {
        log.error("error while parsing google-ads exception", e);
        return null;

private List<AdsCustomer> getAdsCustomerFromStream(
    GoogleAdsServiceClient googleAdsServiceClient,
    SearchGoogleAdsStreamRequest request,
    String customerId
) {
    Callable<List<AdsCustomer>> receiveResponse = () -> {
        ServerStream<SearchGoogleAdsStreamResponse> stream =

        List<AdsCustomer> results = new ArrayList<>();

        for (SearchGoogleAdsStreamResponse response : stream) {
                .forEach(googleAdsRow -> results.add(mapAdsCustomer(customerId, googleAdsRow)));

        return results;

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<List<AdsCustomer>> future = executor.submit(receiveResponse);

    try {
        List<AdsCustomer> results = future.get(5000, TimeUnit.MILLISECONDS);"Successfully got AdsCustomer from stream for {} (the request was successful)", customerId);
        return results;
    } catch (TimeoutException e) {
        log.warn("Failed to get AdsCustomer from stream for {} - the token probably didn't have the right permissions. {}"
            , customerId, e.getMessage());
    } catch (InterruptedException | ExecutionException e) {
        log.warn("Failed to get AdsCustomer from stream for {} (the thread failed). {}", customerId, e.getMessage());
    } finally {

    return ImmutableList.of();
Good luck!
We’re Hiring!
Oribi is growing,
come join our amazing team :)
Check out our Careers Page