{"record":{"id":"5c40226c77d91298","repo":"apache/beam","slug":"structs-are-not-supported-in-mutation","errorCode":null,"errorMessage":"Structs are not supported in mutation.","messagePattern":"Structs are not supported in mutation\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/MutationSizeEstimator.java","lineNumber":48,"sourceCode":"/** Estimates the logical size of {@link com.google.cloud.spanner.Mutation}. */\nclass MutationSizeEstimator {\n\n  // Prevent construction.\n  private MutationSizeEstimator() {}\n\n  /** Estimates a size of mutation in bytes. */\n  static long sizeOf(Mutation m) {\n    if (m.getOperation() == Mutation.Op.DELETE) {\n      return sizeOf(m.getKeySet());\n    }\n    long result = 0;\n    for (Value v : m.getValues()) {\n      switch (v.getType().getCode()) {\n        case ARRAY:\n          result += estimateArrayValue(v);\n          break;\n        case STRUCT:\n          throw new IllegalArgumentException(\"Structs are not supported in mutation.\");\n        default:\n          result += estimatePrimitiveValue(v);\n      }\n    }\n    return result;\n  }\n\n  private static long sizeOf(KeySet keySet) {\n    long result = 0;\n    for (Key k : keySet.getKeys()) {\n      result += sizeOf(k);\n    }\n    for (KeyRange kr : keySet.getRanges()) {\n      result += sizeOf(kr);\n    }\n    return result;\n  }\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/MutationSizeEstimator.java#L30-L66","documentation":"MutationSizeEstimator.sizeOf() estimates the byte size of a Spanner mutation for batching. Spanner mutations cannot contain STRUCT values, so when a mutation's value list has a STRUCT-typed column the estimator throws IllegalArgumentException as a guard.","triggerScenarios":"Building a Spanner mutation (insert/update/delete) whose row values include a STRUCT column, then passing it through the Beam Spanner sink which estimates mutation size.","commonSituations":"Source schema (e.g. Spanner query results or another database) includes STRUCT columns and they are forwarded directly to the Spanner write without flattening; auto-generated pipelines copying full table rows.","solutions":["Flatten STRUCT columns into individual scalar columns before writing.","Remove STRUCT fields from the mutation — they are not writable via Spanner mutations.","If the struct data is needed, serialize it to STRING (e.g. JSON) in a normal column."],"exampleFix":"// before\nmutationBuilder.set(\"address\").to(structValue) // STRUCT not allowed\n// after\nmutationBuilder.set(\"address_json\").to(Value.string(JsonUtils.toString(structValue)))","handlingStrategy":"validation","validationCode":"boolean hasStruct = mutation.size() > 0 && mutation.getValues().stream()\n  .anyMatch(v -> v.getType().getCode() == Type.Code.STRUCT);\nif (hasStruct) throw new IllegalStateException(\"flatten struct columns before writing\");","typeGuard":"boolean structFree(Iterable<Value> values) { return StreamSupport.stream(values.spliterator(), false).noneMatch(v -> v.getType().getCode() == Type.Code.STRUCT); }","tryCatchPattern":"try { size = MutationSizeEstimator.of(mutation); } catch (IllegalArgumentException e) { /* drop/flatten struct columns */ }","preventionTips":["Never forward STRUCT columns into Spanner mutation writes","Pre-serialize nested data to JSON STRING columns","Test pipelines against the real target schema including nested types"],"tags":["java","apache-beam","spanner","data-model"],"backgroundTag":"unsupported-operation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}