Share your feedback about the Google Ads (AdWords) API. Take the 2021 AdWords API and Google Ads API Annual Survey.

The AdWords API will sunset on April 27, 2022. Migrate to the Google Ads API to take advantage of the latest Google Ads features.

Account Management Samples

The code samples below provide examples of common account management functions using the AdWords API. Client Library.

Accept an invitation for linking to a manager account

// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package adwords.axis.v201809.accountmanagement;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.beust.jcommander.Parameter;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.mcm.CustomerServiceInterface;
import com.google.api.ads.adwords.axis.v201809.mcm.ServiceLink;
import com.google.api.ads.adwords.axis.v201809.mcm.ServiceLinkLinkStatus;
import com.google.api.ads.adwords.axis.v201809.mcm.ServiceLinkOperation;
import com.google.api.ads.adwords.axis.v201809.mcm.ServiceType;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.utils.examples.ArgumentNames;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.ads.common.lib.utils.examples.CodeSampleParams;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;

/**
 * This example accepts a pending invitation to link your AdWords account to a Google Merchant
 * Center account.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the "ads.properties" file.
 * See README for more info.
 */
public class AcceptServiceLink {

  private static class AcceptServiceLinkParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.SERVICE_LINK_ID, required = true)
    private Long serviceLinkId;
  }

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    AcceptServiceLinkParams params = new AcceptServiceLinkParams();
    if (!params.parseArguments(args)) {
      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.serviceLinkId = Long.parseLong("INSERT_SERVICE_LINK_ID_HERE");
    }

    try {
      runExample(adWordsServices, session, params.serviceLinkId);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param serviceLinkId the ID of the service link to accept.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices, AdWordsSession session, long serviceLinkId)
      throws RemoteException {
    // Get the CustomerService.
    CustomerServiceInterface customerService =
        adWordsServices.get(session, CustomerServiceInterface.class);

    // Create the operation to set the status to ACTIVE.
    ServiceLinkOperation op = new ServiceLinkOperation();
    op.setOperator(Operator.SET);
    ServiceLink serviceLink = new ServiceLink();
    serviceLink.setServiceLinkId(serviceLinkId);
    serviceLink.setServiceType(ServiceType.MERCHANT_CENTER);
    serviceLink.setLinkStatus(ServiceLinkLinkStatus.ACTIVE);
    op.setOperand(serviceLink);

    // Update the service link.
    ServiceLink[] mutatedServiceLinks =
        customerService.mutateServiceLinks(new ServiceLinkOperation[] {op});

    // Display the results.
    for (ServiceLink mutatedServiceLink : mutatedServiceLinks) {
      System.out.printf(
          "Service link with service link ID %d, type '%s' updated to status: %s.%n",
          mutatedServiceLink.getServiceLinkId(),
          mutatedServiceLink.getServiceType(),
          mutatedServiceLink.getLinkStatus());
    }
  }
}

Create a new account under an AdWords manager

// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package adwords.axis.v201809.accountmanagement;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomer;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomerOperation;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomerReturnValue;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomerServiceInterface;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;
import org.joda.time.DateTime;

/**
 * This example creates a new account under an AdWords manager account.
 *
 * <p>Note: this example must be run using the credentials of an AdWords manager account, and
 * by default the new account will only be accessible via the parent AdWords manager
 * account.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class CreateAccount {

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    try {
      runExample(adWordsServices, session);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices, AdWordsSession session) throws RemoteException {
    // Get the CampaignService.
    ManagedCustomerServiceInterface managedCustomerService =
        adWordsServices.get(session, ManagedCustomerServiceInterface.class);

    // Create account.
    ManagedCustomer customer = new ManagedCustomer();
    customer.setName("Customer created with ManagedCustomerService on " + DateTime.now());
    customer.setCurrencyCode("EUR");
    customer.setDateTimeZone("Europe/London");

    // Create operations.
    ManagedCustomerOperation operation = new ManagedCustomerOperation();
    operation.setOperand(customer);
    operation.setOperator(Operator.ADD);

    ManagedCustomerOperation[] operations = new ManagedCustomerOperation[] {operation};

    // Add account.
    ManagedCustomerReturnValue result = managedCustomerService.mutate(operations);

    // Display accounts.
    for (ManagedCustomer customerResult : result.getValue()) {
      System.out.printf("Account with customer ID %d was created.%n",
          customerResult.getCustomerId());
    }
  }
}

Get all account changes during the past 24 hours

// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package adwords.axis.v201809.accountmanagement;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201809.SelectorBuilder;
import com.google.api.ads.adwords.axis.v201809.ch.AdGroupChangeData;
import com.google.api.ads.adwords.axis.v201809.ch.CampaignChangeData;
import com.google.api.ads.adwords.axis.v201809.ch.ChangeStatus;
import com.google.api.ads.adwords.axis.v201809.ch.CustomerChangeData;
import com.google.api.ads.adwords.axis.v201809.ch.CustomerSyncSelector;
import com.google.api.ads.adwords.axis.v201809.ch.CustomerSyncServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignPage;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.DateTimeRange;
import com.google.api.ads.adwords.axis.v201809.cm.Selector;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.selectorfields.v201809.cm.CampaignField;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

/**
 * This example gets the changes in the account during the last 24 hours.
 */
public class GetAccountChanges {

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    try {
      runExample(adWordsServices, session);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(AdWordsServicesInterface adWordsServices, AdWordsSession session)
      throws RemoteException {
    // Get the CampaignService.
    CampaignServiceInterface campaignService =
        adWordsServices.get(session, CampaignServiceInterface.class);

    // Get the CustomerSyncService.
    CustomerSyncServiceInterface customerSyncService =
        adWordsServices.get(session, CustomerSyncServiceInterface.class);

    // Get a list of all campaign IDs.
    List<Long> campaignIds = new ArrayList<>();
    Selector selector = new SelectorBuilder()
        .fields(CampaignField.Id)
        .build();
    CampaignPage campaigns = campaignService.get(selector);
    if (campaigns.getEntries() != null) {
      Arrays.stream(campaigns.getEntries()).forEach(campaign -> campaignIds.add(campaign.getId()));
    }

    // Create date time range for the past 24 hours.
    DateTimeRange dateTimeRange = new DateTimeRange();
    dateTimeRange.setMin(new SimpleDateFormat("yyyyMMdd HHmmss").format(new Date(System
        .currentTimeMillis() - 1000L * 60 * 60 * 24)));
    dateTimeRange.setMax(new SimpleDateFormat("yyyyMMdd HHmmss").format(new Date()));

    // Create selector.
    CustomerSyncSelector customerSyncSelector = new CustomerSyncSelector();
    customerSyncSelector.setDateTimeRange(dateTimeRange);
    customerSyncSelector
        .setCampaignIds(ArrayUtils.toPrimitive(campaignIds.toArray(new Long[] {})));

    // Get all account changes for campaign.
    CustomerChangeData accountChanges = customerSyncService.get(customerSyncSelector);

    // Display changes.
    if (accountChanges != null && accountChanges.getChangedCampaigns() != null) {
      System.out.printf("Most recent change: %s%n", accountChanges.getLastChangeTimestamp());
      for (CampaignChangeData campaignChanges : accountChanges.getChangedCampaigns()) {
        System.out.printf("Campaign with ID %d was changed:%n", campaignChanges.getCampaignId());
        System.out.printf("\tCampaign changed status: '%s'%n", 
            campaignChanges.getCampaignChangeStatus());
        if (!ChangeStatus.NEW.equals(campaignChanges.getCampaignChangeStatus())) {
          System.out.printf("\tAdded campaign criteria: %s%n",
              getFormattedList(campaignChanges.getAddedCampaignCriteria()));
          System.out.printf("\tRemoved campaign criteria: %s%n",
              getFormattedList(campaignChanges.getRemovedCampaignCriteria()));

          if (campaignChanges.getChangedAdGroups() != null) {
            for (AdGroupChangeData adGroupChanges : campaignChanges.getChangedAdGroups()) {
              System.out.printf("\tAd group with ID %d was changed:%n",
                  adGroupChanges.getAdGroupId());
              System.out.printf("\t\tAd group changed status: %s%n",
                  adGroupChanges.getAdGroupChangeStatus());
              if (!ChangeStatus.NEW.equals(adGroupChanges.getAdGroupChangeStatus())) {
                System.out.printf("\t\tAds changed: %s%n",
                    getFormattedList(adGroupChanges.getChangedAds()));
                System.out.printf("\t\tCriteria changed: %s%n",
                    getFormattedList(adGroupChanges.getChangedCriteria()));
                System.out.printf("\t\tCriteria removed: %s%n",
                    getFormattedList(adGroupChanges.getRemovedCriteria()));
              }
            }
          }
        }
        System.out.println("");
      }
    } else {
      System.out.println("No account changes were found.");
    }
  }

  /**
   * Gets a formatted list of a long array in the form {1,2,3}.
   * @param idList the long array
   * @return the formatted list
   */
  private static String getFormattedList(long[] idList) {
    if (idList == null) {
      idList = new long[]{};
    }
    return new StringBuilder().append("{")
        .append(StringUtils.join(ArrayUtils.toObject(idList), ','))
        .append("}").toString();
  }
}

Get the account hierarchy under the current account

// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package adwords.axis.v201809.accountmanagement;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201809.SelectorBuilder;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomer;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomerLink;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomerPage;
import com.google.api.ads.adwords.axis.v201809.mcm.ManagedCustomerServiceInterface;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.selectorfields.v201809.cm.ManagedCustomerField;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.auth.oauth2.Credential;
import com.google.common.collect.Maps;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;

/**
 * This example gets the account hierarchy under the current account.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class GetAccountHierarchy {
  private static final int PAGE_SIZE = 500;

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    try {
      runExample(adWordsServices, session);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(AdWordsServicesInterface adWordsServices, AdWordsSession session)
      throws RemoteException {
    // Get the ServicedAccountService.
    ManagedCustomerServiceInterface managedCustomerService =
        adWordsServices.get(session, ManagedCustomerServiceInterface.class);

    // Create selector builder.
    int offset = 0;
    SelectorBuilder selectorBuilder =
        new SelectorBuilder()
            .fields(ManagedCustomerField.CustomerId, ManagedCustomerField.Name)
            .offset(offset)
            .limit(PAGE_SIZE);

    // Get results.
    ManagedCustomerPage page;

    // Map from customerId to customer node.
    Map<Long, ManagedCustomerTreeNode> customerIdToCustomerNode = Maps.newHashMap();

    // Map from each parent customer ID to its set of linked child customer IDs.
    SortedSetMultimap<Long, Long> parentIdToChildIds = TreeMultimap.create();
    do {
      page = managedCustomerService.get(selectorBuilder.build());

      if (page.getEntries() != null) {
        // Create account tree nodes for each customer.
        for (ManagedCustomer customer : page.getEntries()) {
          ManagedCustomerTreeNode node = new ManagedCustomerTreeNode();
          node.account = customer;
          customerIdToCustomerNode.put(customer.getCustomerId(), node);
        }

        // Update the map of parent customer ID to child customer IDs.
        if (page.getLinks() != null) {
          for (ManagedCustomerLink link : page.getLinks()) {
            parentIdToChildIds.put(link.getManagerCustomerId(), link.getClientCustomerId());
          }
        }
      }
      offset += PAGE_SIZE;
      selectorBuilder.increaseOffsetBy(PAGE_SIZE);
    } while (offset < page.getTotalNumEntries());

    // Update the parentNode of each child node, and add each child to the childAccounts
    // of its parentNode.
    for (Entry<Long, Long> parentIdEntry : parentIdToChildIds.entries()) {
      ManagedCustomerTreeNode parentNode = customerIdToCustomerNode.get(parentIdEntry.getKey());
      ManagedCustomerTreeNode childNode = customerIdToCustomerNode.get(parentIdEntry.getValue());
      childNode.parentNode = parentNode;
      parentNode.childAccounts.add(childNode);
    }

    // Find the root account node in the tree.
    ManagedCustomerTreeNode rootNode =
        customerIdToCustomerNode.values().stream()
            .filter(node -> node.parentNode == null)
            .findFirst()
            .orElse(null);

    // Display serviced account graph.
    if (rootNode != null) {
      // Display account tree.
      System.out.println("CustomerId, Name");
      System.out.println(rootNode.toTreeString(0, new StringBuffer()));
    } else {
      System.out.println("No serviced accounts were found.");
    }
  }

  /**
   * Example implementation of a node that would exist in an account tree.
   */
  private static class ManagedCustomerTreeNode {
    protected ManagedCustomerTreeNode parentNode;
    protected ManagedCustomer account;
    protected List<ManagedCustomerTreeNode> childAccounts =
        new ArrayList<ManagedCustomerTreeNode>();

    /**
     * Default constructor.
     */
    public ManagedCustomerTreeNode() {}

    @Override
    public String toString() {
      return String.format("%s, %s", account.getCustomerId(), account.getName());
    }

    /**
     * Returns a string representation of the current level of the tree and
     * recursively returns the string representation of the levels below it.
     *
     * @param depth the depth of the node
     * @param sb the string buffer containing the tree representation
     * @return the tree string representation
     */
    public StringBuffer toTreeString(int depth, StringBuffer sb) {
      sb.append(StringUtils.repeat("-", depth * 2)).append(this).append(SystemUtils.LINE_SEPARATOR);
      childAccounts.forEach(childAccount -> childAccount.toTreeString(depth + 1, sb));
      return sb;
    }
  }
}