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.

Remarketing Samples

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

Create a remarketing user list (audience)

// 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.remarketing;

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.AdWordsConversionTracker;
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.ConversionTracker;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerPage;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.Selector;
import com.google.api.ads.adwords.axis.v201809.rm.AdwordsUserListServiceInterface;
import com.google.api.ads.adwords.axis.v201809.rm.BasicUserList;
import com.google.api.ads.adwords.axis.v201809.rm.UserList;
import com.google.api.ads.adwords.axis.v201809.rm.UserListConversionType;
import com.google.api.ads.adwords.axis.v201809.rm.UserListMembershipStatus;
import com.google.api.ads.adwords.axis.v201809.rm.UserListOperation;
import com.google.api.ads.adwords.axis.v201809.rm.UserListReturnValue;
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.AdwordsUserListField;
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.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * This example adds a remarketing user list (a.k.a. audience).
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddAudience {

  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 UserListService.
    AdwordsUserListServiceInterface userListService =
        adWordsServices.get(session, AdwordsUserListServiceInterface.class);

    // Get the ConversionTrackerService.
    ConversionTrackerServiceInterface conversionTrackerService =
        adWordsServices.get(session, ConversionTrackerServiceInterface.class);

    // Create conversion type (tag).
    UserListConversionType conversionType = new UserListConversionType();
    conversionType.setName("Mars cruise customers #" + System.currentTimeMillis());

    // Create remarketing user list.
    BasicUserList userList = new BasicUserList();
    userList.setName("Mars cruise customers #" + System.currentTimeMillis());
    userList.setDescription("A list of mars cruise customers in the last year");
    userList.setMembershipLifeSpan(365L);
    userList.setConversionTypes(new UserListConversionType[] {conversionType});

    // You can optionally provide these field(s).
    userList.setStatus(UserListMembershipStatus.OPEN);

    // Create operations.
    UserListOperation operation = new UserListOperation();
    operation.setOperand(userList);
    operation.setOperator(Operator.ADD);

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

    // Add user list.
    UserListReturnValue result = userListService.mutate(operations);

    // Display results.
    // Capture the ID(s) of the conversion.
    List<String> conversionIds = new ArrayList<>();
    for (UserList userListResult : result.getValue()) {
      if (userListResult instanceof BasicUserList) {
        BasicUserList remarketingUserList = (BasicUserList) userListResult;
        for (UserListConversionType userListConversionType :
            remarketingUserList.getConversionTypes()) {
          conversionIds.add(userListConversionType.getId().toString());
        }
      }
    }

    // Create predicate and selector.
    Selector selector = new SelectorBuilder()
        .fields("Id", "GoogleGlobalSiteTag", "GoogleEventSnippet")
        .in(AdwordsUserListField.Id, conversionIds.toArray(new String[0]))
        .build();

    // Get all conversion trackers.
    Map<Long, AdWordsConversionTracker> conversionTrackers =
        new HashMap<Long, AdWordsConversionTracker>();
    ConversionTrackerPage page = conversionTrackerService.get(selector);
    if (page != null && page.getEntries() != null) {
      conversionTrackers =
          Arrays.stream(page.getEntries())
              .collect(
                  Collectors.toMap(
                      conversionTracker -> conversionTracker.getId(),
                      conversionTracker -> (AdWordsConversionTracker) conversionTracker));
    }

    // Display user lists.
    for (UserList userListResult : result.getValue()) {
      System.out.printf("User list with name '%s' and ID %d was added.%n",
          userListResult.getName(), userListResult.getId());

      // Display user list associated conversion code snippets.
      if (userListResult instanceof BasicUserList) {
        BasicUserList remarketingUserList = (BasicUserList) userListResult;
        for (UserListConversionType userListConversionType : remarketingUserList
            .getConversionTypes()) {
          ConversionTracker conversionTracker =
              conversionTrackers.get(userListConversionType.getId());
          System.out.printf(
              "Google global site tag:%n%s%n%n", conversionTracker.getGoogleGlobalSiteTag());
          System.out.printf(
              "Google event snippet:%n%s%n%n", conversionTracker.getGoogleEventSnippet());
        }
      }
    }
  }
}

Create an AdWords conversion tracker and add to it upload conversions

// 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.remarketing;

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.AdWordsConversionTracker;
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.ConversionTracker;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerCategory;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerOperation;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.ConversionTrackerStatus;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.UploadConversion;
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 java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * This example adds an AdWords conversion and an upload conversion tracker.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddConversionTrackers {

  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 ConversionTrackerService.
    ConversionTrackerServiceInterface service =
        adWordsServices.get(session, ConversionTrackerServiceInterface.class);

    List<ConversionTracker> conversionTrackers = new ArrayList<>();

    // Create an AdWords conversion tracker.
    AdWordsConversionTracker adWordsConversionTracker = new AdWordsConversionTracker();
    adWordsConversionTracker.setName("Earth to Mars Cruises Conversion # "
        + System.currentTimeMillis());
    adWordsConversionTracker.setCategory(ConversionTrackerCategory.DEFAULT);

    // You can optionally provide these field(s).
    adWordsConversionTracker.setStatus(ConversionTrackerStatus.ENABLED);
    adWordsConversionTracker.setViewthroughLookbackWindow(15);
    adWordsConversionTracker.setDefaultRevenueValue(1d);
    adWordsConversionTracker.setAlwaysUseDefaultRevenueValue(Boolean.TRUE);
    conversionTrackers.add(adWordsConversionTracker);

    // Create an upload conversion for offline conversion imports.
    UploadConversion uploadConversion = new UploadConversion();
    // Set an appropriate category. This field is optional, and will be set to
    // DEFAULT if not mentioned.
    uploadConversion.setCategory(ConversionTrackerCategory.LEAD);
    uploadConversion.setName("Upload Conversion #" + System.currentTimeMillis());
    uploadConversion.setViewthroughLookbackWindow(30);
    uploadConversion.setCtcLookbackWindow(90);

    // Optional: Set the default currency code to use for conversions
    // that do not specify a conversion currency. This must be an ISO 4217
    // 3-character currency code such as "EUR" or "USD".
    // If this field is not set on this UploadConversion, AdWords will use
    // the account's currency.
    uploadConversion.setDefaultRevenueCurrencyCode("EUR");

    // Optional: Set the default revenue value to use for conversions
    // that do not specify a conversion value. Note that this value
    // should NOT be in micros.
    uploadConversion.setDefaultRevenueValue(2.50);
    // Optional: To upload fractional conversion credits, mark the upload conversion
    // as externally attributed. See
    // https://developers.google.com/adwords/api/docs/guides/conversion-tracking#importing_externally_attributed_conversions
    // to learn more about importing externally attributed conversions.

    // uploadConversion.setIsExternallyAttributed(true);

    conversionTrackers.add(uploadConversion);

    // Create operations.
    List<ConversionTrackerOperation> operations =
        conversionTrackers.stream()
            .map(
                conversionTracker -> {
                  ConversionTrackerOperation operation = new ConversionTrackerOperation();
                  operation.setOperator(Operator.ADD);
                  operation.setOperand(conversionTracker);
                  return operation;
                })
            .collect(Collectors.toList());

    // Add the conversions.
    ConversionTrackerReturnValue result =
        service.mutate(operations.toArray(new ConversionTrackerOperation[operations.size()]));

    // Display conversion.
    for (ConversionTracker conversionTracker : result.getValue()) {
      System.out.printf(
          "Conversion with ID %d, name '%s', status '%s', " + "category '%s' was added.%n",
          conversionTracker.getId(),
          conversionTracker.getName(),
          conversionTracker.getStatus(),
          conversionTracker.getCategory());
      if (conversionTracker instanceof AdWordsConversionTracker) {
        System.out.printf(
            "Google global site tag:%n%s%n%n", conversionTracker.getGoogleGlobalSiteTag());
        System.out.printf(
            "Google event snippet:%n%s%n%n", conversionTracker.getGoogleEventSnippet());
      }
    }
  }
}

Create and populate a user list

// 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.remarketing;

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.rm.AddressInfo;
import com.google.api.ads.adwords.axis.v201809.rm.AdwordsUserListServiceInterface;
import com.google.api.ads.adwords.axis.v201809.rm.CrmBasedUserList;
import com.google.api.ads.adwords.axis.v201809.rm.CustomerMatchUploadKeyType;
import com.google.api.ads.adwords.axis.v201809.rm.Member;
import com.google.api.ads.adwords.axis.v201809.rm.MutateMembersOperand;
import com.google.api.ads.adwords.axis.v201809.rm.MutateMembersOperation;
import com.google.api.ads.adwords.axis.v201809.rm.MutateMembersReturnValue;
import com.google.api.ads.adwords.axis.v201809.rm.UserList;
import com.google.api.ads.adwords.axis.v201809.rm.UserListOperation;
import com.google.api.ads.adwords.axis.v201809.rm.UserListReturnValue;
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 com.google.common.collect.ImmutableList;
import java.io.UnsupportedEncodingException;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
 * This example adds a user list (a.k.a. audience) and uploads members to populate the list.
 *
 * <p>
 * <em>Note:</em> It may take up to several hours for the list to be populated with members.
 * Email addresses must be associated with a Google account.
 * For privacy purposes, the user list size will show as zero until the list has at least 1,000
 * members. After that, the size will be rounded to the two most significant digits.
 * </p>
 *
 * <p>
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 * </p>
 */
public class AddCrmBasedUserList {

  private static final ImmutableList<String> EMAILS =
      ImmutableList.of("client1@example.com", "client2@example.com", " Client3@example.com ");
  private static final MessageDigest digest = getSHA256MessageDigest();

  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);
    } catch (UnsupportedEncodingException ue) {
      System.err.printf("Example failed due to encoding exception: %s%n", ue);
    }
  }

  /**
   * 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.
   * @throws UnsupportedEncodingException if encoding the hashed email failed.
   */
  public static void runExample(AdWordsServicesInterface adWordsServices, AdWordsSession session)
      throws RemoteException, UnsupportedEncodingException {
    // Get the UserListService.
    AdwordsUserListServiceInterface userListService =
        adWordsServices.get(session, AdwordsUserListServiceInterface.class);

    // Create a user list.
    CrmBasedUserList userList = new CrmBasedUserList();
    userList.setName("Customer relationship management list #" + System.currentTimeMillis());
    userList.setDescription("A list of customers that originated from email addresses");

    // CRM-based user lists can use a membershipLifeSpan of 10000 to indicate unlimited; otherwise
    // normal values apply.
    userList.setMembershipLifeSpan(30L);
    userList.setUploadKeyType(CustomerMatchUploadKeyType.CONTACT_INFO);

    // Create operation.
    UserListOperation operation = new UserListOperation();
    operation.setOperand(userList);
    operation.setOperator(Operator.ADD);

    // Add user list.
    UserListReturnValue result = userListService.mutate(new UserListOperation[] {operation});

    // Display user list.
    UserList userListAdded = result.getValue(0);
    System.out.printf(
        "User list with name '%s' and ID %d was added.%n",
        userListAdded.getName(), userListAdded.getId());

    // Get user list ID.
    Long userListId = userListAdded.getId();

    // Create operation to add members to the user list based on email addresses.
    MutateMembersOperation mutateMembersOperation = new MutateMembersOperation();
    MutateMembersOperand operand = new MutateMembersOperand();
    operand.setUserListId(userListId);

    // Hash normalized email addresses based on SHA-256 hashing algorithm.
    List<Member> members = new ArrayList<>(EMAILS.size());
    for (String email : EMAILS) {
      String normalizedEmail = toNormalizedString(email);
      Member member = new Member();
      member.setHashedEmail(toSHA256String(normalizedEmail));
      members.add(member);
    }

    String firstName = "John";
    String lastName = "Doe";
    String countryCode = "US";
    String zipCode = "10011";

    AddressInfo addressInfo = new AddressInfo();
    // First and last name must be normalized and hashed.
    addressInfo.setHashedFirstName(toSHA256String(toNormalizedString(firstName)));
    addressInfo.setHashedLastName(toSHA256String(toNormalizedString(lastName)));
    // Country code and zip code are sent in plaintext.
    addressInfo.setCountryCode(countryCode);
    addressInfo.setZipCode(zipCode);

    Member memberByAddress = new Member();
    memberByAddress.setAddressInfo(addressInfo);
    members.add(memberByAddress);

    operand.setMembersList(members.toArray(new Member[members.size()]));
    mutateMembersOperation.setOperand(operand);
    mutateMembersOperation.setOperator(Operator.ADD);

    // Add members to the user list based on email addresses.
    MutateMembersReturnValue mutateMembersResult =
        userListService.mutateMembers(new MutateMembersOperation[] {mutateMembersOperation});

    // Display results.
    // Reminder: it may take several hours for the list to be populated with members.
    for (UserList userListResult : mutateMembersResult.getUserLists()) {
      System.out.printf(
          "%d email addresses were uploaded to user list with name '%s' and ID %d "
              + "and are scheduled for review.%n",
          EMAILS.size(), userListResult.getName(), userListResult.getId());
    }
  }

  /**
   * Hash a string using SHA-256 hashing algorithm.
   *
   * @param str the string to hash.
   * @return the SHA-256 hash string representation.
   * @throws UnsupportedEncodingException If UTF-8 charset is not supported.
   */
  private static String toSHA256String(String str) throws UnsupportedEncodingException {
    byte[] hash = digest.digest(str.getBytes("UTF-8"));
    StringBuilder result = new StringBuilder();
    for (byte b : hash) {
      result.append(String.format("%02x", b));
    }

    return result.toString();
  }

  /**
   * Removes leading and trailing whitespace and converts all characters to lower case.
   *
   * @param value the string to normalize.
   * @return a normalized copy of the string.
   */
  private static String toNormalizedString(String value) {
    return value.trim().toLowerCase();
  }

  /** Returns SHA-256 hashing algorithm. */
  private static MessageDigest getSHA256MessageDigest() {
    try {
      return MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("Missing SHA-256 algorithm implementation.", e);
    }
  }
}

Create rule-based user lists

// 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.remarketing;

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.rm.AdwordsUserListServiceInterface;
import com.google.api.ads.adwords.axis.v201809.rm.CombinedRuleUserList;
import com.google.api.ads.adwords.axis.v201809.rm.CombinedRuleUserListRuleOperator;
import com.google.api.ads.adwords.axis.v201809.rm.DateKey;
import com.google.api.ads.adwords.axis.v201809.rm.DateRuleItem;
import com.google.api.ads.adwords.axis.v201809.rm.DateRuleItemDateOperator;
import com.google.api.ads.adwords.axis.v201809.rm.DateSpecificRuleUserList;
import com.google.api.ads.adwords.axis.v201809.rm.ExpressionRuleUserList;
import com.google.api.ads.adwords.axis.v201809.rm.NumberKey;
import com.google.api.ads.adwords.axis.v201809.rm.NumberRuleItem;
import com.google.api.ads.adwords.axis.v201809.rm.NumberRuleItemNumberOperator;
import com.google.api.ads.adwords.axis.v201809.rm.Rule;
import com.google.api.ads.adwords.axis.v201809.rm.RuleBasedUserListPrepopulationStatus;
import com.google.api.ads.adwords.axis.v201809.rm.RuleItem;
import com.google.api.ads.adwords.axis.v201809.rm.RuleItemGroup;
import com.google.api.ads.adwords.axis.v201809.rm.StringKey;
import com.google.api.ads.adwords.axis.v201809.rm.StringRuleItem;
import com.google.api.ads.adwords.axis.v201809.rm.StringRuleItemStringOperator;
import com.google.api.ads.adwords.axis.v201809.rm.UserList;
import com.google.api.ads.adwords.axis.v201809.rm.UserListOperation;
import com.google.api.ads.adwords.axis.v201809.rm.UserListReturnValue;
import com.google.api.ads.adwords.axis.v201809.rm.UserListRuleTypeEnumsEnum;
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 java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.joda.time.DateTime;

/**
 * This example adds two rule-based remarketing user lists: one with no site visit date
 * restrictions, and another that will only include users who visit your site in the
 * next six months.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddRuleBasedUserLists {

  private static final String DATE_FORMAT_STRING = "yyyyMMdd";
  
  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 AdwordsUserListService.
    AdwordsUserListServiceInterface userListService =
        adWordsServices.get(session, AdwordsUserListServiceInterface.class);
   
    // First rule item group - users who visited the checkout page and had more than one item
    // in their shopping cart.
    StringKey pageTypeKey = new StringKey("ecomm_pagetype");
    
    StringRuleItem checkoutStringRuleItem = new StringRuleItem();
    checkoutStringRuleItem.setKey(pageTypeKey);
    checkoutStringRuleItem.setOp(StringRuleItemStringOperator.EQUALS);
    checkoutStringRuleItem.setValue("checkout");
    
    RuleItem checkoutRuleItem = new RuleItem();
    checkoutRuleItem.setStringRuleItem(checkoutStringRuleItem);

    NumberKey cartSizeKey = new NumberKey("cartsize");
    
    NumberRuleItem cartSizeNumberRuleItem = new NumberRuleItem();
    cartSizeNumberRuleItem.setKey(cartSizeKey);
    cartSizeNumberRuleItem.setOp(NumberRuleItemNumberOperator.GREATER_THAN);
    cartSizeNumberRuleItem.setValue(1.0);

    RuleItem cartSizeRuleItem = new RuleItem();
    cartSizeRuleItem.setNumberRuleItem(cartSizeNumberRuleItem);

    // Combine the two rule items into a RuleItemGroup so AdWords will AND their rules
    // together.
    RuleItemGroup checkoutMultipleItemGroup = new RuleItemGroup();
    checkoutMultipleItemGroup.setItems(new RuleItem[] {checkoutRuleItem, cartSizeRuleItem});

    // Second rule item group - users who checked out within the next 3 months.
    DateKey checkoutDateKey = new DateKey("checkoutdate");
    
    DateRuleItem startDateDateRuleItem = new DateRuleItem();
    startDateDateRuleItem.setKey(checkoutDateKey);
    startDateDateRuleItem.setOp(DateRuleItemDateOperator.AFTER);
    startDateDateRuleItem.setValue(DateTime.now().toString(DATE_FORMAT_STRING));

    RuleItem startDateRuleItem = new RuleItem();
    startDateRuleItem.setDateRuleItem(startDateDateRuleItem);

    DateRuleItem endDateDateRuleItem = new DateRuleItem();
    endDateDateRuleItem.setKey(checkoutDateKey);
    endDateDateRuleItem.setOp(DateRuleItemDateOperator.BEFORE);
    endDateDateRuleItem.setValue(DateTime.now().plusMonths(3).toString(DATE_FORMAT_STRING));

    RuleItem endDateRuleItem = new RuleItem();
    endDateRuleItem.setDateRuleItem(endDateDateRuleItem);

    // Combine the date rule items into a RuleItemGroup.
    RuleItemGroup checkedOutNextThreeMonthsItemGroup = new RuleItemGroup();
    checkedOutNextThreeMonthsItemGroup.setItems(
        new RuleItem[] {startDateRuleItem, endDateRuleItem});

    // Combine the rule item groups into a Rule so AdWords knows how to apply the rules.
    Rule rule = new Rule();
    rule.setGroups(
        new RuleItemGroup[] {checkoutMultipleItemGroup, checkedOutNextThreeMonthsItemGroup});
    // ExpressionRuleUserLists can use either CNF or DNF for matching. CNF means 'at least one item
    // in each rule item group must match', and DNF means 'at least one entire rule item group must
    // match'. DateSpecificRuleUserList only supports DNF. You can also omit the rule type
    // altogether to default to DNF.
    rule.setRuleType(UserListRuleTypeEnumsEnum.DNF);

    // Third and fourth rule item groups.
    // Visitors of a page who visited another page.
    StringKey urlStringKey = new StringKey("url__");

    StringRuleItem site1StringRuleItem = new StringRuleItem();
    site1StringRuleItem.setKey(urlStringKey);
    site1StringRuleItem.setOp(StringRuleItemStringOperator.EQUALS);
    site1StringRuleItem.setValue("example.com/example1");
    RuleItem site1RuleItem = new RuleItem();
    site1RuleItem.setStringRuleItem(site1StringRuleItem);

    StringRuleItem site2StringRuleItem = new StringRuleItem();
    site2StringRuleItem.setKey(urlStringKey);
    site2StringRuleItem.setOp(StringRuleItemStringOperator.EQUALS);
    site2StringRuleItem.setValue("example.com/example2");
    RuleItem site2RuleItem = new RuleItem();
    site2RuleItem.setStringRuleItem(site2StringRuleItem);

    // Create two RuleItemGroups to show that a visitor browsed two sites.
    RuleItemGroup site1RuleItemGroup = new RuleItemGroup();
    site1RuleItemGroup.setItems(new RuleItem[]{site1RuleItem});
    RuleItemGroup site2RuleItemGroup = new RuleItemGroup();
    site2RuleItemGroup.setItems(new RuleItem[]{site2RuleItem});

    // Create two rules to show that a visitor browsed two sites.
    Rule userVisitedSite1Rule = new Rule();
    userVisitedSite1Rule.setGroups(new RuleItemGroup[]{site1RuleItemGroup});

    Rule userVisitedSite2Rule = new Rule();
    userVisitedSite2Rule.setGroups(new RuleItemGroup[]{site2RuleItemGroup});

    // Create the user list with no restrictions on site visit date.
    ExpressionRuleUserList expressionUserList = new ExpressionRuleUserList();
    String creationTimeString = DateTime.now().toString("yyyyMMdd_HHmmss");
    expressionUserList.setName(
        "Expression based user list created at " + creationTimeString);
    expressionUserList.setDescription(
        "Users who checked out in three month window OR visited the checkout page "
        + "with more than one item in their cart");
    expressionUserList.setRule(rule);

    // Optional: Set the prepopulationStatus to REQUESTED to include past users in the user list.
    expressionUserList.setPrepopulationStatus(RuleBasedUserListPrepopulationStatus.REQUESTED);

    // Create the user list restricted to users who visit your site within the next six months.
    DateTime startDate = DateTime.now();
    DateTime endDate = startDate.plusMonths(6);

    DateSpecificRuleUserList dateUserList = new DateSpecificRuleUserList();
    dateUserList.setName(
        "Date rule user list created at " + creationTimeString);
    dateUserList.setDescription(String.format("Users who visited the site between %s and %s and "
        + "checked out in three month window OR visited the checkout page "
        + "with more than one item in their cart", startDate.toString(DATE_FORMAT_STRING),
        endDate.toString(DATE_FORMAT_STRING)));
    dateUserList.setRule(rule);

    // Set the start and end dates of the user list.
    dateUserList.setStartDate(startDate.toString(DATE_FORMAT_STRING));
    dateUserList.setEndDate(endDate.toString(DATE_FORMAT_STRING));

    // Create the user list where "Visitors of a page who did visit another page".
    // To create a user list where "Visitors of a page who did not visit another
    // page", change the ruleOperator from AND to AND_NOT.
    CombinedRuleUserList combinedRuleUserList = new CombinedRuleUserList();
    combinedRuleUserList.setName("Combined rule user list created at " + creationTimeString);
    combinedRuleUserList.setDescription("Users who visited two sites.");
    combinedRuleUserList.setLeftOperand(userVisitedSite1Rule);
    combinedRuleUserList.setRightOperand(userVisitedSite2Rule);
    combinedRuleUserList.setRuleOperator(CombinedRuleUserListRuleOperator.AND);

    // Create operations to add the user lists.
    List<UserListOperation> operations =
        Stream.of(expressionUserList, dateUserList, combinedRuleUserList)
            .map(
                userList -> {
                  UserListOperation operation = new UserListOperation();
                  operation.setOperand(userList);
                  operation.setOperator(Operator.ADD);
                  return operation;
                })
            .collect(Collectors.toList());

    // Submit the operations.
    UserListReturnValue result =
        userListService.mutate(operations.toArray(new UserListOperation[operations.size()]));

    // Display the results.
    for (UserList userListResult : result.getValue()) {
      System.out.printf("User list added with ID %d, name '%s', status '%s', list type '%s',"
          + " accountUserListStatus '%s', description '%s'.%n",
          userListResult.getId(),
          userListResult.getName(),
          userListResult.getStatus().getValue(),
          userListResult.getListType() == null ? null : userListResult.getListType().getValue(),
          userListResult.getAccountUserListStatus().getValue(),
          userListResult.getDescription());
    }
  }
}

Import conversion adjustments for existing conversions

// Copyright 2018 Google LLC
//
// 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
//
//     https://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.remarketing;

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.GclidOfflineConversionAdjustmentFeed;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionAdjustmentFeed;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionAdjustmentFeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionAdjustmentFeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionAdjustmentFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionAdjustmentType;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
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;
import javax.annotation.Nullable;

/**
 * This code example imports conversion adjustments for conversions that already exist. To set up a
 * conversion tracker, run the AddConversionTrackers.java example.
 */
public class UploadConversionAdjustment {

  private static class UploadOfflineConversionsParams extends CodeSampleParams {
    @Parameter(
        names = ArgumentNames.CONVERSION_NAME,
        required = true,
        description = "Name of the conversion tracker.")
    private String conversionName;

    @Parameter(names = ArgumentNames.GCL_ID, required = true)
    private String gclId;

    @Parameter(names = ArgumentNames.ADJUSTMENT_TYPE, required = true)
    private String adjustmentType;

    @Parameter(
        names = ArgumentNames.CONVERSION_TIME,
        required = true,
        description = "Conversion time should be after the click time.")
    private String conversionTime;

    @Parameter(names = ArgumentNames.ADJUSTMENT_TIME, required = true)
    private String adjustmentTime;

    @Parameter(
        names = ArgumentNames.ADJUSTED_VALUE,
        description = "Adjusted value for restatements.")
    private Double adjustedValue = null;
  }

  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();

    UploadOfflineConversionsParams params = new UploadOfflineConversionsParams();
    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.conversionName = "INSERT_CONVERSION_NAME_HERE";
      params.gclId = "INSERT_GCL_ID_HERE";
      params.adjustmentType = "INSERT_ADJUSTMENT_TYPE_HERE";
      params.conversionTime = "INSERT_CONVERSION_TIME_HERE";
      params.adjustmentTime = "INSERT_ADJUSTMENT_TIME_HERE";
      // Optional: Adjusted value for adjustment type RESTATE.
      params.adjustedValue = Double.parseDouble("INSERT_ADJUSTED_VALUE_HERE");
    }

    OfflineConversionAdjustmentType adjustmentTypeEnum =
        OfflineConversionAdjustmentType.fromString(params.adjustmentType);

    try {
      runExample(
          adWordsServices,
          session,
          params.conversionName,
          params.gclId,
          adjustmentTypeEnum,
          params.conversionTime,
          params.adjustmentTime,
          params.adjustedValue);
    } 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 conversionName the name of the conversion tracker.
   * @param gclId the GCLID for the conversion.
   * @param adjustmentType the type of adjustment.
   * @param adjustmentTime the date and time of the adjustment.
   * @param conversionTime the date and time of the conversion.
   * @param adjustedValue the adjusted value.
   * @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,
      String conversionName,
      String gclId,
      OfflineConversionAdjustmentType adjustmentType,
      String conversionTime,
      String adjustmentTime,
      @Nullable Double adjustedValue)
      throws RemoteException {

    // Get the OfflineConversionAdjustmentFeedService.
    OfflineConversionAdjustmentFeedServiceInterface offlineConversionFeedService =
        adWordsServices.get(session, OfflineConversionAdjustmentFeedServiceInterface.class);

    // Associate conversion adjustments with the existing named conversion tracker. The GCLID should
    // have been uploaded before with a conversion.
    GclidOfflineConversionAdjustmentFeed feed = new GclidOfflineConversionAdjustmentFeed();
    feed.setConversionName(conversionName);
    feed.setAdjustmentType(adjustmentType);
    feed.setConversionTime(conversionTime);
    feed.setAdjustmentTime(adjustmentTime);
    feed.setAdjustedValue(adjustedValue);
    feed.setGoogleClickId(gclId);

    OfflineConversionAdjustmentFeedOperation offlineConversionOperation =
        new OfflineConversionAdjustmentFeedOperation();
    offlineConversionOperation.setOperator(Operator.ADD);
    offlineConversionOperation.setOperand(feed);

    OfflineConversionAdjustmentFeedReturnValue offlineConversionReturnValue =
        offlineConversionFeedService.mutate(
            new OfflineConversionAdjustmentFeedOperation[] {offlineConversionOperation});

    for (OfflineConversionAdjustmentFeed newFeed : offlineConversionReturnValue.getValue()) {
      System.out.printf(
          "Uploaded conversion adjusted value of %.4f for Google Click ID '%s'.%n",
          newFeed.getAdjustedValue(),
          ((GclidOfflineConversionAdjustmentFeed) newFeed).getGoogleClickId());
    }
  }
}

Import offline call conversions

// 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.remarketing;

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.OfflineCallConversionFeed;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineCallConversionFeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineCallConversionFeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineCallConversionFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
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;

/**
 * This example imports offline call conversion values for calls related to the ads in your account.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the "ads.properties" file.
 * See README for more info.
 */
public class UploadOfflineCallConversions {

  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();

    // Replace with valid values of your account.
    String callerId = "INSERT_CALLER_ID_HERE";

    // For times, use the format yyyyMMdd HHmmss tz. For more details on formats, see:
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
    // For time zones, see:
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
    String callStartTime = "INSERT_CALL_START_TIME_HERE";

    // Name of the conversion tracker to upload to.
    String conversionName = "INSERT_CONVERSION_NAME_HERE";

    String conversionTime = "INSERT_CONVERSION_TIME_HERE";
    Double conversionValue = Double.valueOf("INSERT_CONVERSION_VALUE_HERE");

    try {
      runExample(
          adWordsServices,
          session,
          callerId,
          callStartTime,
          conversionName,
          conversionTime,
          conversionValue);
    } 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 callerId the caller ID of the call.
   * @param callStartTime the call start time of the call.
   * @param conversionName the name of the conversion tracker.
   * @param conversionTime the date and time of the conversion.
   * @param conversionValue the value of the conversion.
   * @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,
      String callerId,
      String callStartTime,
      String conversionName,
      String conversionTime,
      double conversionValue)
      throws RemoteException {

    // Get the OfflineCallConversionFeedService.
    OfflineCallConversionFeedServiceInterface offlineCallConversionFeedService =
        adWordsServices.get(session, OfflineCallConversionFeedServiceInterface.class);

    // Associate offline call conversions with the existing named conversion tracker. If this
    // tracker was newly created, it may be a few hours before it can accept conversions.
    OfflineCallConversionFeed feed = new OfflineCallConversionFeed();
    feed.setCallerId(callerId);
    feed.setCallStartTime(callStartTime);
    feed.setConversionName(conversionName);
    feed.setConversionTime(conversionTime);
    feed.setConversionValue(conversionValue);

    OfflineCallConversionFeedOperation offlineCallConversionOperation =
        new OfflineCallConversionFeedOperation();
    offlineCallConversionOperation.setOperator(Operator.ADD);
    offlineCallConversionOperation.setOperand(feed);

    // This example uploads only one call conversion, but you can upload multiple call conversions
    // by passing additional operations.
    OfflineCallConversionFeedReturnValue offlineCallConversionReturnValue =
        offlineCallConversionFeedService.mutate(
            new OfflineCallConversionFeedOperation[] {offlineCallConversionOperation});

    // Display results.
    for (OfflineCallConversionFeed feedResult : offlineCallConversionReturnValue.getValue()) {
      System.out.printf(
          "Uploaded offline conversion value of %.4f for caller ID '%s'.%n",
          feedResult.getConversionValue(), feedResult.getCallerId());
    }
  }
}

Import offline click conversions

// 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.remarketing;

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.OfflineConversionFeed;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionFeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionFeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.OfflineConversionFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
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 code example imports offline conversion values for specific clicks to
 * your account. To get Google Click ID for a click, run
 * CLICK_PERFORMANCE_REPORT. To set up a conversion tracker, run the
 * AddConversionTrackers.java example.
 */
public class UploadOfflineConversions {

  private static class UploadOfflineConversionsParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.CONVERSION_NAME, required = true,
        description = "Name of the conversion tracker to upload to.")
    private String conversionName;

    @Parameter(names = ArgumentNames.GCL_ID, required = true,
        description = "Google Click ID should be newer than 30 days.")
    private String gclId;

    @Parameter(names = ArgumentNames.CONVERSION_TIME, required = true,
        description = "Conversion time should be after the click time.")
    private String conversionTime;

    @Parameter(names = ArgumentNames.CONVERSION_VALUE, required = true)
    private Double conversionValue;
  }

  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();



    UploadOfflineConversionsParams params = new UploadOfflineConversionsParams();
    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.conversionName = "INSERT_CONVERSION_NAME_HERE";
      params.gclId = "INSERT_GCL_ID_HERE";
      params.conversionTime = "INSERT_CONVERSION_TIME_HERE";
      params.conversionValue = Double.parseDouble("INSERT_CONVERSION_VALUE_HERE");
    }

    try {
      runExample(
          adWordsServices,
          session,
          params.conversionName,
          params.gclId,
          params.conversionTime,
          params.conversionValue);
    } 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 conversionName the name of the conversion tracker.
   * @param gClid the GCLID for the conversion.
   * @param conversionTime the date and time of the conversion.
   * @param conversionValue the value of the conversion.
   * @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,
      String conversionName, String gClid, String conversionTime,
      double conversionValue) throws RemoteException {

    // Get the OfflineConversionFeedService.
    OfflineConversionFeedServiceInterface offlineConversionFeedService =
        adWordsServices.get(session, OfflineConversionFeedServiceInterface.class);

    // Associate offline conversions with the existing named conversion tracker. If this tracker
    // was newly created, it may be a few hours before it can accept conversions.
    OfflineConversionFeed feed = new OfflineConversionFeed();
    feed.setConversionName(conversionName);
    feed.setConversionTime(conversionTime);
    feed.setConversionValue(conversionValue);
    feed.setGoogleClickId(gClid);

    OfflineConversionFeedOperation offlineConversionOperation =
        new OfflineConversionFeedOperation();
    offlineConversionOperation.setOperator(Operator.ADD);
    offlineConversionOperation.setOperand(feed);

    OfflineConversionFeedReturnValue offlineConversionReturnValue = offlineConversionFeedService
        .mutate(new OfflineConversionFeedOperation[] {offlineConversionOperation});

    OfflineConversionFeed newFeed = offlineConversionReturnValue.getValue(0);

    System.out.printf(
        "Uploaded offline conversion value of %.4f for Google Click ID '%s' to '%s'.%n",
        newFeed.getConversionValue(), newFeed.getGoogleClickId(), newFeed.getConversionName());
  }
}

Upload offline data for store sales transactions

// 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.remarketing;

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.FieldPathElement;
import com.google.api.ads.adwords.axis.v201809.cm.Money;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.rm.FirstPartyUploadMetadata;
import com.google.api.ads.adwords.axis.v201809.rm.MoneyWithCurrency;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineData;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineDataUpload;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineDataUploadOperation;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineDataUploadReturnValue;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineDataUploadServiceInterface;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineDataUploadType;
import com.google.api.ads.adwords.axis.v201809.rm.OfflineDataUploadUserIdentifierType;
import com.google.api.ads.adwords.axis.v201809.rm.StoreSalesTransaction;
import com.google.api.ads.adwords.axis.v201809.rm.StoreSalesUploadCommonMetadata;
import com.google.api.ads.adwords.axis.v201809.rm.ThirdPartyUploadMetadata;
import com.google.api.ads.adwords.axis.v201809.rm.UploadMetadata;
import com.google.api.ads.adwords.axis.v201809.rm.UserIdentifier;
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 com.google.common.collect.ImmutableSet;
import java.io.UnsupportedEncodingException;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.joda.time.DateTime;

/**
 * This code example shows how to upload offline data for store sales transactions.
 *
 * <p>
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 * </p>
 */
public class UploadOfflineData {

  /** User identifier types whose values must be hashed. */
  private static final ImmutableSet<OfflineDataUploadUserIdentifierType> HASHED_IDENTIFIER_TYPES =
      ImmutableSet.of(
          OfflineDataUploadUserIdentifierType.HASHED_EMAIL,
          OfflineDataUploadUserIdentifierType.HASHED_FIRST_NAME,
          OfflineDataUploadUserIdentifierType.HASHED_LAST_NAME,
          OfflineDataUploadUserIdentifierType.HASHED_PHONE);

  /** Digest to use for hashing values. */
  private static final MessageDigest digest = getSHA256MessageDigest();

  private static final class UploadOfflineDataParams extends CodeSampleParams {

    /**
     * Email addresses for the transactions. Exactly two addresses required. You can pass these on
     * the command line using either of the following options:
     *
     * <ol>
     *   <li>Separate parameter name/value pairs, e.g., {@code --emailAddress address1
     *       --emailAddress address2}
     *   <li>A single name/value pair where the value is comma-separated, e.g., {@code
     *       --emailAddress address1,address2}
     * </ol>
     */
    @Parameter(
      names = ArgumentNames.EMAIL_ADDRESS,
      description = "Email addresses for the transactions. Exactly two addresses required.",
      required = true
    )
    private List<String> emailAddresses;

    @Parameter(names = ArgumentNames.CONVERSION_NAME, required = true)
    private String conversionName;

    @Parameter(names = ArgumentNames.EXTERNAL_UPLOAD_ID, required = true)
    private Long externalUploadId;

    @Parameter(names = ArgumentNames.OFFLINE_DATA_UPLOAD_TYPE)
    private String offlineDataUploadType =
        OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY.getValue();

    @Parameter(
      names = ArgumentNames.UPLOAD_TIME,
      description = "Only required if uploading third party data."
    )
    private String advertiserUploadTime;

    @Parameter(
      names = ArgumentNames.BRIDGE_MAP_VERSION_ID,
      description = "Only required if uploading third party data."
    )
    private String bridgeMapVersionId;

    @Parameter(
      names = ArgumentNames.PARTNER_ID,
      description = "Only required if uploading third party data."
    )
    private Integer partnerId;
  }

  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();

    UploadOfflineDataParams params = new UploadOfflineDataParams();
    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.emailAddresses =
          Arrays.asList("INSERT_EMAIL_ADDRESS_1_HERE", "INSERT_EMAIL_ADDRESS_2_HERE");
      params.conversionName = "INSERT_CONVERSION_NAME_HERE";
      params.externalUploadId = Long.valueOf("INSERT_EXTERNAL_UPLOAD_ID_HERE");

      // Change the enum below to STORE_SALES_UPLOAD_THIRD_PARTY if uploading third party data.
      params.offlineDataUploadType =
          OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY.getValue();

      // The three constants below are needed when uploading third party data.
      // You can safely ignore them if you are uploading first party data.
      // For times, use the format yyyyMMdd HHmmss tz. For more details on formats, see:
      // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
      // For time zones, see:
      // https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
      params.advertiserUploadTime = "INSERT_ADVERTISER_UPLOAD_TIME";
      params.bridgeMapVersionId = "INSERT_BRIDGE_MAP_VERSION_ID";
      params.partnerId = Integer.valueOf("INSERT_PARTNER_ID");
    }

    OfflineDataUploadType uploadTypeEnum =
        OfflineDataUploadType.fromString(params.offlineDataUploadType);

    // Set partial failure to true since this example demonstrates how to handle partial
    // failure errors.
    session.setPartialFailure(true);
    try {
      runExample(
          adWordsServices,
          session,
          params.conversionName,
          params.externalUploadId,
          params.emailAddresses,
          uploadTypeEnum,
          params.advertiserUploadTime,
          params.bridgeMapVersionId,
          params.partnerId);
    } 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);
    } catch (UnsupportedEncodingException ue) {
      System.err.printf("Example failed due to encoding exception: %s%n", ue);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param conversionName the name of the conversion tracker.
   * @param externalUploadId the external upload ID.
   * @param emailAddresses the list of email addresses. Must contain exactly two entries for this
   *     example.
   * @param offlineDataUploadType the type of store sales upload metadata to upload.
   * @param advertiserUploadTime the date and time of the advertiser upload.
   * @param bridgeMapVersionId the bridge map version ID.
   * @param partnerId the partner ID.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   * @throws UnsupportedEncodingException if encoding the offline data values failed.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      String conversionName,
      long externalUploadId,
      List<String> emailAddresses,
      OfflineDataUploadType offlineDataUploadType,
      String advertiserUploadTime,
      String bridgeMapVersionId,
      Integer partnerId)
      throws RemoteException, UnsupportedEncodingException {
    // This example requires exactly 2 email addresses.
    if (emailAddresses.size() != 2) {
      throw new IllegalArgumentException(
          String.format(
              "%d email addresses specified. Please specify exactly 2 email addresses.",
              emailAddresses.size()));
    }

    // Get the OfflineDataUploadService.
    OfflineDataUploadServiceInterface offlineDataUploadService =
        adWordsServices.get(session, OfflineDataUploadServiceInterface.class);

    List<OfflineData> offlineDataList = new ArrayList<>();

    // Create the first offline data for upload.
    // This transaction occurred 7 days ago with amount of 200 USD.
    DateTime transactionTime1 = DateTime.now().minusDays(7);
    long transactionAmount1 = 200_000_000L;
    List<UserIdentifier> userIdentifiers1 =
        Arrays.asList(
            createUserIdentifier(
                OfflineDataUploadUserIdentifierType.HASHED_EMAIL, emailAddresses.get(0)),
            createUserIdentifier(OfflineDataUploadUserIdentifierType.STATE, "New York"));

    offlineDataList.add(
        createOfflineData(
            transactionTime1, transactionAmount1, "USD", conversionName, userIdentifiers1));

    // Create the second offline data for upload.
    // This transaction occurred 14 days ago with amount of 450 EUR.
    DateTime transactionTime2 = DateTime.now().minusDays(14);
    long transactionAmount2 = 450_000_000L;

    List<UserIdentifier> userIdentifiers2 =
        Arrays.asList(
            createUserIdentifier(
                OfflineDataUploadUserIdentifierType.HASHED_EMAIL, emailAddresses.get(1)),
            createUserIdentifier(OfflineDataUploadUserIdentifierType.STATE, "California"));

    offlineDataList.add(
        createOfflineData(
            transactionTime2, transactionAmount2, "EUR", conversionName, userIdentifiers2));

    // Create offline data upload object.
    OfflineDataUpload offlineDataUpload = new OfflineDataUpload();
    offlineDataUpload.setExternalUploadId(externalUploadId);
    offlineDataUpload.setOfflineDataList(
        offlineDataList.toArray(new OfflineData[offlineDataList.size()]));
    offlineDataUpload.setUploadType(offlineDataUploadType);

    // Set the type and metadata of this upload.
    StoreSalesUploadCommonMetadata storeSalesUploadMetadata;
    if (OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY.equals(offlineDataUploadType)) {
      storeSalesUploadMetadata = new FirstPartyUploadMetadata();
    } else {
      ThirdPartyUploadMetadata thirdPartyUploadMetadata = new ThirdPartyUploadMetadata();
      thirdPartyUploadMetadata.setAdvertiserUploadTime(advertiserUploadTime);
      thirdPartyUploadMetadata.setValidTransactionRate(1.0);
      thirdPartyUploadMetadata.setPartnerMatchRate(1.0);
      thirdPartyUploadMetadata.setPartnerUploadRate(1.0);
      thirdPartyUploadMetadata.setBridgeMapVersionId(bridgeMapVersionId);
      thirdPartyUploadMetadata.setPartnerId(partnerId);
      storeSalesUploadMetadata = thirdPartyUploadMetadata;
    }
    storeSalesUploadMetadata.setLoyaltyRate(1.0);
    storeSalesUploadMetadata.setTransactionUploadRate(1.0);

    UploadMetadata uploadMetadata = new UploadMetadata();
    uploadMetadata.setStoreSalesUploadCommonMetadata(storeSalesUploadMetadata);
    offlineDataUpload.setUploadMetadata(uploadMetadata);

    // Create an offline data upload operation.
    List<OfflineDataUploadOperation> operations = new ArrayList<>();
    OfflineDataUploadOperation offlineDataUploadOperation = new OfflineDataUploadOperation();
    offlineDataUploadOperation.setOperator(Operator.ADD);
    offlineDataUploadOperation.setOperand(offlineDataUpload);
    operations.add(offlineDataUploadOperation);

    // Upload offline data on the server and print some information.
    OfflineDataUploadReturnValue returnValue =
        offlineDataUploadService.mutate(operations.toArray(new OfflineDataUploadOperation[0]));
    offlineDataUpload = returnValue.getValue(0);
    System.out.printf(
        "Uploaded offline data with external upload ID %d, and upload status %s.%n",
        offlineDataUpload.getExternalUploadId(), offlineDataUpload.getUploadStatus());

    // Print any partial failure errors from the response.
    if (returnValue.getPartialFailureErrors() != null) {
      for (ApiError apiError : returnValue.getPartialFailureErrors()) {
        // Get the index of the failed operation from the error's field path elements.
        Integer operationIndex = getFieldPathElementIndex(apiError, "operations");
        if (operationIndex != null) {
          OfflineDataUpload failedOfflineDataUpload = operations.get(operationIndex).getOperand();
          // Get the index of the entry in the offline data list from the error's field path
          // elements.
          Integer offlineDataListIndex = getFieldPathElementIndex(apiError, "offlineDataList");
          System.out.printf(
              "Offline data list entry %d in operation %d with external upload ID %d and type "
                  + "'%s' has triggered a failure for the following reason: '%s'.%n",
              offlineDataListIndex,
              operationIndex,
              failedOfflineDataUpload.getExternalUploadId(),
              failedOfflineDataUpload.getUploadType(),
              apiError.getErrorString());
        } else {
          System.out.printf(
              "A failure has occurred for the following reason: %s%n", apiError.getErrorString());
        }
      }
    }
  }

  /**
   * Returns the {@link FieldPathElement#getIndex()} for the specified {@code field} name, if
   * present in the error's field path elements.
   *
   * @param apiError the error to inspect.
   * @param field the name of the field to search for in the error's field path elements.
   * @return the index of the entry with the specified field, or {@code null} if no such entry
   *     exists or the entry has a null index.
   */
  private static Integer getFieldPathElementIndex(ApiError apiError, String field) {
    FieldPathElement[] fieldPathElements = apiError.getFieldPathElements();
    if (fieldPathElements == null) {
      return null;
    }
    for(int i = 0; i < fieldPathElements.length; i++) {
      FieldPathElement fieldPathElement = fieldPathElements[i];
      if (field.equals(fieldPathElement.getField())) {
        return fieldPathElement.getIndex();
      }
    }
    return null;
  }

  /** Returns a new offline data object with the specified values. */
  private static OfflineData createOfflineData(
      DateTime transactionTime,
      long transactionMicroAmount,
      String transactionCurrency,
      String conversionName,
      List<UserIdentifier> userIdentifiers) {
    StoreSalesTransaction storeSalesTransaction = new StoreSalesTransaction();

    // For times use the format yyyyMMdd HHmmss [tz].
    // For details, see
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
    storeSalesTransaction.setTransactionTime(transactionTime.toString("yyyyMMdd HHmmss ZZZ"));
    storeSalesTransaction.setConversionName(conversionName);
    storeSalesTransaction.setUserIdentifiers(
        userIdentifiers.toArray(new UserIdentifier[userIdentifiers.size()]));

    Money money = new Money();
    money.setMicroAmount(transactionMicroAmount);

    MoneyWithCurrency moneyWithCurrency = new MoneyWithCurrency();
    moneyWithCurrency.setMoney(money);
    moneyWithCurrency.setCurrencyCode(transactionCurrency);
    storeSalesTransaction.setTransactionAmount(moneyWithCurrency);

    OfflineData offlineData = new OfflineData();
    offlineData.setStoreSalesTransaction(storeSalesTransaction);
    return offlineData;
  }

  /** Returns a new user identifier with the specified type and value. */
  private static UserIdentifier createUserIdentifier(
      OfflineDataUploadUserIdentifierType identifierType, String value)
      throws UnsupportedEncodingException {
    // If the user identifier type is a hashed type, also call hash function
    // on the value.
    if (HASHED_IDENTIFIER_TYPES.contains(identifierType)) {
      value = toSHA256String(value);
    }
    UserIdentifier userIdentifier = new UserIdentifier();
    userIdentifier.setUserIdentifierType(identifierType);
    userIdentifier.setValue(value);
    return userIdentifier;
  }

  /**
   * Hash a string using SHA-256 hashing algorithm.
   *
   * @param str the string to hash.
   * @return the SHA-256 hash string representation.
   * @throws UnsupportedEncodingException If UTF-8 charset is not supported.
   */
  private static String toSHA256String(String str) throws UnsupportedEncodingException {
    // Normalize the string before hashing.
    byte[] hash = digest.digest(toNormalizedString(str).getBytes("UTF-8"));
    StringBuilder result = new StringBuilder();
    for (byte b : hash) {
      result.append(String.format("%02x", b));
    }

    return result.toString();
  }

  /**
   * Removes leading and trailing whitespace and converts all characters to lower case.
   *
   * @param value the string to normalize.
   * @return a normalized copy of the string.
   */
  private static String toNormalizedString(String value) {
    return value.trim().toLowerCase();
  }

  /** Returns SHA-256 hashing algorithm. */
  private static MessageDigest getSHA256MessageDigest() {
    try {
      return MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("Missing SHA-256 algorithm implementation.", e);
    }
  }
}