Code review comment: it's kinda both! If the source is unbounded (streaming) -  and the groupingFactor has not been specified by the user, then default to no grouping.

Code before 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("To Global Window", Window.into(new GlobalWindows()))
.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 under review: ParDo.of(

Code after addressing the 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);
}