{"record":{"id":"58b2d0aa2d1e5a96","repo":"apache/druid","slug":"table-type-is-required","errorCode":null,"errorMessage":"Table type is required","messagePattern":"Table type is required","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/catalog/model/TableSpec.java","lineNumber":103,"sourceCode":"    return properties;\n  }\n\n  @JsonProperty(\"columns\")\n  @JsonInclude(Include.NON_NULL)\n  public List<ColumnSpec> columns()\n  {\n    return columns;\n  }\n\n  /**\n   * Validate the final spec. Updates use this same class, but allow\n   * the spec to be partial (and thus inconsistent). Validation should\n   * be done on the merged result, not on the updates themselves.\n   */\n  public void validate()\n  {\n    if (Strings.isNullOrEmpty(type)) {\n      throw new IAE(\"Table type is required\");\n    }\n  }\n\n  @Override\n  public String toString()\n  {\n    return CatalogUtils.toString(this);\n  }\n\n  @Override\n  public boolean equals(Object o)\n  {\n    if (o == this) {\n      return true;\n    }\n    if (o == null || o.getClass() != getClass()) {\n      return false;\n    }","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/catalog/model/TableSpec.java#L85-L121","documentation":"TableSpec.validate() checks that every table spec declares its type, since the type selects the table definition that interprets the spec. An empty or null type makes the spec uninterpretable, so validate() throws IAE. Note that update specs intentionally may omit type; validation is meant to run on merged, complete specs.","triggerScenarios":"Calling TableSpec.validate() on a spec whose type field is null/empty — e.g. a partial update spec validated directly instead of being merged first, or JSON missing the \"type\" field.","commonSituations":"Validating update payloads (which are allowed to have null type) instead of the merged result; hand-written spec JSON omitting \"type\"; deserialization binding failures.","solutions":["Set the type field on the spec before validating","If validating an update, merge it with the base spec first and validate the merged TableSpec instead","Check the source JSON includes the \"type\" property"],"exampleFix":"// before\nTableSpec update = new TableSpec(null, props, null);\nupdate.validate(); // throws\n// after\nTableSpec merged = base.merge(base, update, mapper);\nmerged.validate(); // validate the merged, complete spec","handlingStrategy":"validation","validationCode":"if (Strings.isNullOrEmpty(spec.type())) {\n  throw new IllegalArgumentException(\"spec.type must be set before validate()\");\n}","typeGuard":"boolean isCompleteSpec(TableSpec spec) {\n  return spec != null && spec.type() != null && !spec.type().isEmpty();\n}","tryCatchPattern":"try { spec.validate(); } catch (IllegalArgumentException e) { if (e.getMessage().equals(\"Table type is required\")) { /* merge with base or set type */ } else { throw e; } }","preventionTips":["Validate only complete (merged) specs, not update fragments","Always set type when constructing standalone specs","Enforce the type field in spec JSON at deserialization"],"tags":["validation","catalog","missing-field"],"backgroundTag":"missing-required-config-field","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}