code: private void handleAttemptSuccess(List<MutateRowsResponse> responses) {
      List<FailedMutation> allFailures = Lists.newArrayList(permanentFailures);
      MutateRowsRequest lastRequest = currentRequest;
  
      Builder builder = lastRequest.toBuilder().clearEntries();
      List<Integer> newOriginalIndexes = Lists.newArrayList();
  
      for (MutateRowsResponse response : responses) {
        for (Entry entry : response.getEntriesList()) {
          if (entry.getStatus().getCode() == Code.OK_VALUE) {
            continue;
          }
  
          int origIndex = getOriginalIndex((int) entry.getIndex());
  
          FailedMutation failedMutation =
              FailedMutation.create(origIndex, createEntryError(entry.getStatus()));
  
          allFailures.add(failedMutation);
  
          if (!failedMutation.getError().isRetryable()) {
            permanentFailures.add(failedMutation);
          } else {
            // Schedule the mutation entry for the next RPC by adding it to the request builder and
            // recording it's original index
            newOriginalIndexes.add(origIndex);
            builder.addEntries(lastRequest.getEntries((int) entry.getIndex()));
          }
        }
      }
  
      currentRequest = builder.build();
      originalIndexes = newOriginalIndexes;
  
      if (!allFailures.isEmpty()) {
        boolean isRetryable = builder.getEntriesCount() > 0;
        throw new MutateRowsException(null, allFailures, isRetryable);
      }
    }
docs: Handle entry level failures. All new response entries are inspected for failure. If any
  transient failures are found, their corresponding mutations are scheduled for the next RPC. The
  caller is notified of both new found errors and pre-existing permanent errors in the thrown
  {@link MutateRowsException}. If no errors exist, then the attempt future is successfully
  completed.