Code review comment: I was thinking of just having the loop  ``` for (T newValue : newValues) {   valueCoder.encode(newValue, out);   if (out.size() > BAG_APPEND_BATCHING_LIMIT) {     [send out.toByteStringAndReset()]   } } ... ```  rather than introducing the (I think correct, but complex to reason about) `consumePrefixToByteString`. True, we will fail on a element of size 100-x MB after writing x MB, for x < 10, but the question is whether that corner case is worth the additional complexity.

Code under review:
public void asyncClose() throws Exception {
    checkState(
        !isClosed,
        "Bag user state is no longer usable because it is closed for %s",
        request.getStateKey());
    isClosed = true;
    if (!isCleared && newValues.isEmpty()) {
      return;
    }
    if (isCleared) {
      beamFnStateClient.handle(
          request.toBuilder().setClear(StateClearRequest.getDefaultInstance()));
    }
    if (!newValues.isEmpty()) {
      ByteStringOutputStream out = new ByteStringOutputStream();
      for (T newValue : newValues) {
        int previousSize = out.size();
        valueCoder.encode(newValue, out);
        if (out.size() > BAG_APPEND_BATCHING_LIMIT && previousSize > 0) {
          
          beamFnStateClient.handle(
              request
                  .toBuilder()
                  .setAppend(
                      StateAppendRequest.newBuilder()
                          .setData(out.consumePrefixToByteString(previousSize))));
        }
        if (out.size() > BAG_APPEND_BATCHING_LIMIT) {
          
          beamFnStateClient.handle(
              request
                  .toBuilder()
                  .setAppend(StateAppendRequest.newBuilder().setData(out.toByteStringAndReset())));
        }
      }
      if (out.size() > 0) {
        beamFnStateClient.handle(
            request
                .toBuilder()
                .setAppend(StateAppendRequest.newBuilder().setData(out.toByteStringAndReset())));
      }
    }

    
    if (isCleared) {
      
      
      oldValues.clearAndAppend(newValues);
    } else {
      oldValues.append(newValues);
    }
  }

Line commented on: if (out.size() > BAG_APPEND_BATCHING_LIMIT) {

Revised code:
public void asyncClose() throws Exception {
    checkState(
        !isClosed,
        "Bag user state is no longer usable because it is closed for %s",
        request.getStateKey());
    isClosed = true;
    if (!isCleared && newValues.isEmpty()) {
      return;
    }
    if (isCleared) {
      beamFnStateClient.handle(
          request.toBuilder().setClear(StateClearRequest.getDefaultInstance()));
    }
    if (!newValues.isEmpty()) {
      
      
      
      ByteStringOutputStream out = new ByteStringOutputStream();
      for (T newValue : newValues) {
        int previousSize = out.size();
        valueCoder.encode(newValue, out);
        if (out.size() > BAG_APPEND_BATCHING_LIMIT && previousSize > 0) {
          
          
          beamFnStateClient.handle(
              request
                  .toBuilder()
                  .setAppend(
                      StateAppendRequest.newBuilder()
                          .setData(out.consumePrefixToByteString(previousSize))));
        }
        if (out.size() > BAG_APPEND_BATCHING_LIMIT) {
          
          
          
          beamFnStateClient.handle(
              request
                  .toBuilder()
                  .setAppend(StateAppendRequest.newBuilder().setData(out.toByteStringAndReset())));
        }
      }
      if (out.size() > 0) {
        beamFnStateClient.handle(
            request
                .toBuilder()
                .setAppend(StateAppendRequest.newBuilder().setData(out.toByteStringAndReset())));
      }
    }

    
    if (isCleared) {
      
      
      oldValues.clearAndAppend(newValues);
    } else {
      oldValues.append(newValues);
    }
  }