pentaho/pentaho-kettle · error · KettleException

AvroInput.Error.UnsupportedTopLevelStructure

AvroInput.Error.UnsupportedTopLevelStructure

Error message

AvroInput.Error.UnsupportedTopLevelStructure

What it means

AvroNestedReader.initTopLevelStructure only supports Avro schemas whose top-level type is RECORD, UNION (containing at least one record), ARRAY, or MAP. Any other top-level type (e.g. STRING, INT, BYTES, DOUBLE, ENUM, FIXED) is not readable as a row container, so a KettleException is thrown during step init. This is a hard schema-shape restriction of the Avro Input step, not a data error.

Solutions

  1. Rewrap the data in a top-level RECORD: define a record with a field of the primitive/enum type and reference that record as the schema root.
  2. If the top-level type is a UNION, ensure it contains at least one RECORD branch (the reader uses the first record branch to seed the top-level object).
  3. If you only need the scalar value, use a different step (e.g. a User Defined Java Expression or a simpler file input) instead of the Avro Input step.

Example fix

// before: top-level enum schema
{"type": "enum", "name": "Color", "symbols": ["RED","GREEN"]}
// after: wrap in a record
{"type": "record", "name": "ColorRec", "fields": [{"name": "color", "type": {"type": "enum", "name": "Color", "symbols": ["RED","GREEN"]}}]}
Defensive patterns

Strategy: validation

Validate before calling

// Before init, validate the schema root type
Schema.Type t = schema.getType();
if (!(t == Schema.Type.RECORD || t == Schema.Type.ARRAY || t == Schema.Type.MAP
      || (t == Schema.Type.UNION && schema.getTypes().stream()
          .anyMatch(s -> s.getType() == Schema.Type.RECORD)))) {
  throw new IllegalArgumentException("Unsupported top-level Avro schema type: " + t);
}

Type guard

boolean hasSupportedRoot(Schema s) {
  Schema.Type t = s.getType();
  return t == Schema.Type.RECORD || t == Schema.Type.ARRAY || t == Schema.Type.MAP
      || (t == Schema.Type.UNION && s.getTypes().stream()
           .anyMatch(u -> u.getType() == Schema.Type.RECORD));
}

Try / catch

try {
  step.init(...);
} catch (KettleException e) {
  if (e.getMessage().contains("UnsupportedTopLevelStructure")) {
    // fall back to a record-wrapped schema or skip this input
  } else throw e;
}

Prevention

When it happens

Trigger: Setting the step's schema to an Avro schema whose root type is a primitive (string/int/boolean/etc.), enum, or fixed type, then calling init() -> setSchemaToUse() -> initTopLevelStructure().

Common situations: Pointing the Avro Input step at a schema file meant for serializing a single scalar value; a hand-edited schema where the record was unwrapped to a top-level enum/fixed; tooling that generated a minimal schema wrapping one primitive.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/892ecfbc447922fb. Report an issue: GitHub.

Appendix: source

Thrown at plugins/avro-format/core/src/main/java/org/pentaho/di/trans/steps/avro/input/AvroNestedReader.java:332

      }

      m_topLevelRecord = new GenericData.Record( firstUnion );
      if ( setDefault ) {
        m_defaultTopLevelObject = m_topLevelRecord;
      }
    } else if ( schema.getType() == Schema.Type.ARRAY ) {
      m_topLevelArray = new GenericData.Array( 1, schema ); // capacity,
      // schema
      if ( setDefault ) {
        m_defaultTopLevelObject = m_topLevelArray;
      }
    } else if ( schema.getType() == Schema.Type.MAP ) {
      m_topLevelMap = new HashMap<Utf8, Object>();
      if ( setDefault ) {
        m_defaultTopLevelObject = m_topLevelMap;
      }
    } else {
      throw new KettleException( BaseMessages.getString( PKG,
        "AvroInput.Error.UnsupportedTopLevelStructure" ) );
    }
  }

  /**
   * Examines the user-specified paths for the presence of a map/array expansion. If such an expansion is detected it
   * checks that it is valid and, if so, creates an expansion handler for processing it.
   *
   * @param normalFields  the original user-specified paths. This is modified to contain only non-expansion paths.
   * @param outputRowMeta the output row format
   * @return an AvroArrayExpansion object to handle expansions or null if no expansions are present in the user-supplied
   * path definitions.
   * @throws KettleException if a problem occurs
   */
  protected AvroArrayExpansion checkFieldPaths( List<AvroInputField> normalFields,
                                                RowMetaInterface outputRowMeta ) throws
    KettleException {
    // here we check whether there are any full map/array expansions

View on GitHub (pinned to f3058517a1)