docs: public CloudantClient build() {
  
          logger.config("Building client using URL: " + url);
  
          //Build properties and couchdb client
          CouchDbProperties props = new CouchDbProperties(url);
  
          props.addRequestInterceptors(USER_AGENT_INTERCEPTOR);
  
          if (this.iamApiKey != null) {
  
              // Create IAM cookie interceptor and set in HttpConnection interceptors
              IamCookieInterceptor cookieInterceptor = new IamCookieInterceptor(this.iamApiKey,
                      this.url.toString());
  
              props.addRequestInterceptors(cookieInterceptor);
              props.addResponseInterceptors(cookieInterceptor);
              logger.config("Added IAM cookie interceptor");
  
              if (this.iamServerClientId != null && this.iamServerClientSecret != null) {
                  props.addRequestInterceptors(new IamServerBasicAuthInterceptor(
                          cookieInterceptor.getIamServerUrl(), iamServerClientId, iamServerClientSecret));
              }
  
          }
          //Create cookie interceptor
          else if (this.username != null && this.password != null) {
              //make interceptor if both username and password are not null
  
              //Create cookie interceptor and set in HttpConnection interceptors
              CookieInterceptor cookieInterceptor = new CookieInterceptor(username, password, this.url.toString());
  
              props.addRequestInterceptors(cookieInterceptor);
              props.addResponseInterceptors(cookieInterceptor);
              logger.config("Added cookie interceptor");
          } else {
              //If username or password is null, throw an exception
              if (username != null || password != null) {
                  //Username and password both have to contain values
                  throw new CouchDbException("Either a username and password must be provided, or " +
                          "both values must be null. Please check the credentials and try again.");
              }
          }
  
          //If setter methods for read and connection timeout are not called, default values
          // are used.
          logger.config(String.format("Connect timeout: %s %s", connectTimeout,
                  connectTimeoutUnit));
          logger.config(String.format("Read timeout: %s %s", readTimeout, readTimeoutUnit));
  
          // Log a warning if the DNS cache time is too long
          try {
              boolean shouldLogValueWarning = false;
              boolean isUsingDefaultTTLValue = true;
              String ttlString = Security.getProperty("networkaddress.cache.ttl");
              // Was able to access the property
              if (ttlString != null) {
                  try {
                      int ttl = Integer.parseInt(ttlString);
                      isUsingDefaultTTLValue = false;
                      logger.finest("networkaddress.cache.ttl was " + ttl);
                      if (ttl > 30 || ttl < 0) {
                          shouldLogValueWarning = true;
                      }
                  } catch (NumberFormatException nfe) {
                      // Suppress the exception, this will result in the default being used
                      logger.finest("networkaddress.cache.ttl was not an int.");
                  }
              }
  
              if (isUsingDefaultTTLValue && System.getSecurityManager() != null) {
                  //If we're using a default value and there is a SecurityManager we need to warn
                  shouldLogValueWarning = true;
              }
  
              if (shouldLogValueWarning) {
                  logger.warning("DNS cache lifetime may be too long. DNS cache lifetimes in excess" +
                          " of 30 seconds may impede client operation during cluster failover.");
              }
          } catch (SecurityException e) {
              // Couldn't access the property; log a warning
              logger.warning("Permission denied to check Java DNS cache TTL. If the cache " +
                      "lifetime is too long cluster failover will be impeded.");
          }
  
          props.addRequestInterceptors(new TimeoutCustomizationInterceptor(connectTimeout,
                  connectTimeoutUnit, readTimeout, readTimeoutUnit));
  
          //Set connect options
          props.setMaxConnections(maxConnections);
          props.setProxyURL(proxyURL);
          if (proxyUser != null) {
              //if there was proxy auth information set up proxy auth
              if ("http".equals(url.getProtocol())) {
                  // If we are using http, create an interceptor to add the Proxy-Authorization header
                  props.addRequestInterceptors(new ProxyAuthInterceptor(proxyUser,
                          proxyPassword));
                  logger.config("Added proxy auth interceptor");
              } else {
                  // Set up an authenticator
                  props.setProxyAuthentication(new PasswordAuthentication(proxyUser,
                          proxyPassword.toCharArray()));
              }
          }
          if (isSSLAuthenticationDisabled) {
              props.addRequestInterceptors(SSLCustomizerInterceptor
                      .SSL_AUTH_DISABLED_INTERCEPTOR);
              logger.config("SSL authentication is disabled");
          }
          if (authenticatedModeSSLSocketFactory != null) {
              props.addRequestInterceptors(new SSLCustomizerInterceptor(
                      authenticatedModeSSLSocketFactory
              ));
              logger.config("Added custom SSL socket factory");
          }
  
          //Set http connection interceptors
          if (requestInterceptors != null) {
              for (HttpConnectionRequestInterceptor requestInterceptor : requestInterceptors) {
                  props.addRequestInterceptors(requestInterceptor);
                  logger.config("Added request interceptor: " + requestInterceptor.getClass()
                          .getName());
              }
          }
          if (responseInterceptors != null) {
              for (HttpConnectionResponseInterceptor responseInterceptor : responseInterceptors) {
                  props.addResponseInterceptors(responseInterceptor);
                  logger.config("Added response interceptor: " + responseInterceptor.getClass()
                          .getName());
              }
          }
  
          //if no gsonBuilder has been provided, create a new one
          if (gsonBuilder == null) {
              gsonBuilder = new GsonBuilder();
              logger.config("Using default GSON builder");
          } else {
              logger.config("Using custom GSON builder");
          }
          //always register additional TypeAdapaters for derserializing some Cloudant specific
          // types before constructing the CloudantClient
          gsonBuilder.registerTypeAdapter(DeserializationTypes.SHARDS, new ShardDeserializer())
                  .registerTypeAdapter(DeserializationTypes.INDICES, new IndexDeserializer())
                  .registerTypeAdapter(DeserializationTypes.PERMISSIONS_MAP, new
                          SecurityDeserializer())
                  .registerTypeAdapter(Key.ComplexKey.class, new Key.ComplexKeyDeserializer());
  
          return new CloudantClient(props, gsonBuilder);
      }