{"record":{"id":"d0f7e313ac189f73","repo":"apache/beam","slug":"schema-can-t-be-empty","errorCode":null,"errorMessage":"Schema can't be empty","messagePattern":"Schema can't be empty","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/SpannerTransformRegistrar.java","lineNumber":232,"sourceCode":"            return mode == READ_TIMESTAMP\n                ? TimestampBound.ofReadTimestamp(Timestamp.parseTimestamp(readTimestamp))\n                : TimestampBound.ofMinReadTimestamp(Timestamp.parseTimestamp(readTimestamp));\n          default:\n            throw new IllegalArgumentException(\"Unknown timestamp bound mode: \" + mode);\n        }\n      }\n\n      public ReadOperation getReadOperation() {\n        if (sql != null && table != null) {\n          throw new IllegalStateException(\n              \"Query and table params are mutually exclusive. Set just one of them.\");\n        }\n        ReadOperation readOperation = ReadOperation.create();\n        if (sql != null) {\n          return readOperation.withQuery(sql);\n        }\n        if (Schema.builder().build().equals(schema)) {\n          throw new IllegalArgumentException(\"Schema can't be empty\");\n        }\n        if (table != null) {\n          return readOperation.withTable(table).withColumns(schema.getFieldNames());\n        }\n        throw new IllegalStateException(\"Can't happen\");\n      }\n    }\n\n    @Override\n    @NonNull\n    public PTransform<PBegin, PCollection<Row>> buildExternal(\n        ReadBuilder.Configuration configuration) {\n      configuration.checkMandatoryFields();\n\n      SpannerIO.Read readTransform =\n          SpannerIO.read()\n              .withProjectId(configuration.projectId)\n              .withDatabaseId(configuration.databaseId)","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerTransformRegistrar.java#L214-L250","documentation":"When a table read is configured (no SQL), getReadOperation() uses the schema's field names as the columns to fetch. It throws IllegalArgumentException(\"Schema can't be empty\") when the schema equals an empty Schema.builder().build(), i.e. no fields were defined — a table read without columns is meaningless for the connector.","triggerScenarios":"Calling SpannerIO.read().withTable(\"my_table\") without .withSchema(...) or with an empty schema, or building the schema from a config/JSON list of columns that was empty.","commonSituations":"Users assuming the connector will auto-discover all columns when reading a table (it requires an explicit column list via schema); deserialized schemas where fields array was empty; migration from query-based reads where schema was optional.","solutions":["Add .withSchema(Schema.builder().addInt64Field(\"id\").addStringField(\"name\").build()) listing the columns you want.","Alternatively use .withQuery(\"SELECT * FROM table\") if you want all columns without declaring a schema.","Ensure the columns in the schema match actual Spanner table column names (withColumns uses schema.getFieldNames())."],"exampleFix":"// before\nSpannerIO.read().withInstanceId(\"i\").withDatabaseId(\"db\").withTable(\"users\")\n// after\nSpannerIO.read().withTable(\"users\").withSchema(Schema.builder().addInt64Field(\"id\").addStringField(\"name\").build())","handlingStrategy":"validation","validationCode":"if (sql == null && (schema == null || schema.getFields().isEmpty())) { throw new IllegalArgumentException(\"table reads require a non-empty schema listing columns\"); }","typeGuard":null,"tryCatchPattern":"try { ReadOperation op = config.getReadOperation(); } catch (IllegalArgumentException e) { throw new ConfigException(\"Spanner table read needs columns: \" + e.getMessage(), e); }","preventionTips":["Always declare the columns you need via Schema when reading a table.","Switch to withQuery(\"SELECT * FROM table\") when you truly want all columns.","Validate schema field names against the live table schema during pipeline setup."],"tags":["java","apache-beam","google-cloud-spanner","schema","validation"],"backgroundTag":"empty-required-field","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}