Code review comment: It would be nice if this were another constant. We could have `DEFAULT_GROUPING_FACTOR_BOUNDED` and `DEFAULT_GROUPING_FACTOR_UNBOUNDED`. It doesn't need to be done here, could be in a follow-up PR.

Code under review:
public SpannerWriteResult expand(PCollection<MutationGroup> input) {

      
      PCollection<Void> schemaSeed =
          input.getPipeline().apply("Create Seed", Create.of((Void) null));
      if (spec.getSchemaReadySignal() != null) {
        
        schemaSeed = schemaSeed.apply("Wait for schema", Wait.on(spec.getSchemaReadySignal()));
      }
      final PCollectionView<SpannerSchema> schemaView =
          schemaSeed
              .apply(
                  "Read information schema",
                  ParDo.of(new ReadSpannerSchema(spec.getSpannerConfig())))
              .apply("Schema View", View.asSingleton());

      
      
      PCollectionTuple filteredMutations =
          input
              .apply(
                  "RewindowIntoGlobal",
                  Window.<MutationGroup>into(new GlobalWindows())
                      .triggering(DefaultTrigger.of())
                      .discardingFiredPanes())
              .apply(
                  "Filter Unbatchable Mutations",
                  ParDo.of(
                          new BatchableMutationFilterFn(
                              schemaView,
                              UNBATCHABLE_MUTATIONS_TAG,
                              spec.getBatchSizeBytes(),
                              spec.getMaxNumMutations(),
                              spec.getMaxNumRows()))
                      .withSideInputs(schemaView)
                      .withOutputTags(
                          BATCHABLE_MUTATIONS_TAG, TupleTagList.of(UNBATCHABLE_MUTATIONS_TAG)));

      
      
      PCollection<Iterable<MutationGroup>> batchedMutations =
          filteredMutations
              .get(BATCHABLE_MUTATIONS_TAG)
              .apply(
                  "Gather And Sort",
                  ParDo.of(
                          new GatherBundleAndSortFn(
                              spec.getBatchSizeBytes(),
                              spec.getMaxNumMutations(),
                              spec.getMaxNumRows(),
                              
                              spec.getGroupingFactor()
                                  .orElse(
                                      input.isBounded() == IsBounded.BOUNDED
                                          ? DEFAULT_GROUPING_FACTOR
                                          : 1),
                              schemaView))
                      .withSideInputs(schemaView))
              .apply(
                  "Create Batches",
                  ParDo.of(
                          new BatchFn(
                              spec.getBatchSizeBytes(),
                              spec.getMaxNumMutations(),
                              spec.getMaxNumRows(),
                              schemaView))
                      .withSideInputs(schemaView));

      
      PCollectionTuple result =
          PCollectionList.of(filteredMutations.get(UNBATCHABLE_MUTATIONS_TAG))
              .and(batchedMutations)
              .apply("Merge", Flatten.pCollections())
              .apply(
                  "Write mutations to Spanner",
                  ParDo.of(
                          new WriteToSpannerFn(
                              spec.getSpannerConfig(), spec.getFailureMode(), FAILED_MUTATIONS_TAG))
                      .withOutputTags(MAIN_OUT_TAG, TupleTagList.of(FAILED_MUTATIONS_TAG)));

      return new SpannerWriteResult(
          input.getPipeline(),
          result.get(MAIN_OUT_TAG),
          result.get(FAILED_MUTATIONS_TAG),
          FAILED_MUTATIONS_TAG);
    }

Line commented on: : 1),

Revised code:
public SpannerWriteResult expand(PCollection<MutationGroup> input) {

      
      PCollection<Void> schemaSeed =
          input.getPipeline().apply("Create Seed", Create.of((Void) null));
      if (spec.getSchemaReadySignal() != null) {
        
        schemaSeed = schemaSeed.apply("Wait for schema", Wait.on(spec.getSchemaReadySignal()));
      }
      final PCollectionView<SpannerSchema> schemaView =
          schemaSeed
              .apply(
                  "Read information schema",
                  ParDo.of(new ReadSpannerSchema(spec.getSpannerConfig())))
              .apply("Schema View", View.asSingleton());

      
      
      PCollectionTuple filteredMutations =
          input
              .apply(
                  "RewindowIntoGlobal",
                  Window.<MutationGroup>into(new GlobalWindows())
                      .triggering(DefaultTrigger.of())
                      .discardingFiredPanes())
              .apply(
                  "Filter Unbatchable Mutations",
                  ParDo.of(
                          new BatchableMutationFilterFn(
                              schemaView,
                              UNBATCHABLE_MUTATIONS_TAG,
                              spec.getBatchSizeBytes(),
                              spec.getMaxNumMutations(),
                              spec.getMaxNumRows()))
                      .withSideInputs(schemaView)
                      .withOutputTags(
                          BATCHABLE_MUTATIONS_TAG, TupleTagList.of(UNBATCHABLE_MUTATIONS_TAG)));

      
      
      PCollection<Iterable<MutationGroup>> batchedMutations =
          filteredMutations
              .get(BATCHABLE_MUTATIONS_TAG)
              .apply(
                  "Gather And Sort",
                  ParDo.of(
                          new GatherBundleAndSortFn(
                              spec.getBatchSizeBytes(),
                              spec.getMaxNumMutations(),
                              spec.getMaxNumRows(),
                              
                              spec.getGroupingFactor()
                                  .orElse(
                                      input.isBounded() == IsBounded.BOUNDED
                                          ? DEFAULT_GROUPING_FACTOR
                                          : 1),
                              schemaView))
                      .withSideInputs(schemaView))
              .apply(
                  "Create Batches",
                  ParDo.of(
                          new BatchFn(
                              spec.getBatchSizeBytes(),
                              spec.getMaxNumMutations(),
                              spec.getMaxNumRows(),
                              schemaView))
                      .withSideInputs(schemaView));

      
      PCollectionTuple result =
          PCollectionList.of(filteredMutations.get(UNBATCHABLE_MUTATIONS_TAG))
              .and(batchedMutations)
              .apply("Merge", Flatten.pCollections())
              .apply(
                  "Write mutations to Spanner",
                  ParDo.of(
                          new WriteToSpannerFn(
                              spec.getSpannerConfig(), spec.getFailureMode(), FAILED_MUTATIONS_TAG))
                      .withOutputTags(MAIN_OUT_TAG, TupleTagList.of(FAILED_MUTATIONS_TAG)));

      return new SpannerWriteResult(
          input.getPipeline(),
          result.get(MAIN_OUT_TAG),
          result.get(FAILED_MUTATIONS_TAG),
          FAILED_MUTATIONS_TAG);
    }