This is an automated email from the ASF dual-hosted git repository. voonhous pushed a commit to branch release-1.2.1 in repository https://gitbox.apache.org/repos/asf/hudi.git
commit a9b96378ecc8e87bfc700de5d60d8e48ef76f472 Author: voonhous <[email protected]> AuthorDate: Wed Sep 16 04:17:25 2026 +0800 fix(common): resolve nested Avro records by class built by Hudi (#19956) (cherry picked from commit e19a990e3dec6820453e22914c2c5ce4bd545dab) --- .../java/org/apache/hudi/avro/HoodieAvroUtils.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java b/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java index 4077e1788e90..d97fe8fdcb60 100644 --- a/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java +++ b/hudi-common/src/main/java/org/apache/hudi/avro/HoodieAvroUtils.java @@ -1557,11 +1557,27 @@ public class HoodieAvroUtils { } } + /** + * Loads the generated class for a nested RECORD schema with {@code Class.forName}, bypassing the + * {@code ClassSecurityValidator} check that Avro 1.12.2+ runs in {@code ClassUtils.forName} (reached from + * {@link SpecificData#getClass(Schema)}), which rejects Hudi's generated classes. + * + * <p>Only pass schemas taken from a compiled SCHEMA$, never a schema read from storage, which is what the validation guards against. + */ + private static Class<? extends SpecificRecordBase> getSpecificRecordClass(Schema recordSchema, SpecificData specificData) { + String className = SpecificData.getClassName(recordSchema); + try { + return Class.forName(className, false, specificData.getClassLoader()).asSubclass(SpecificRecordBase.class); + } catch (ClassNotFoundException e) { + throw new HoodieException("Failed to load SpecificRecord class " + className + " for Avro schema " + recordSchema.getFullName(), e); + } + } + private static Object convertFieldToSpecificRecordValue(Schema fieldSchema, Object value, SpecificData specificData) { Schema resolvedFieldSchema = getActualSchemaFromUnion(fieldSchema, value); switch (resolvedFieldSchema.getType()) { case RECORD: - value = convertToSpecificRecord(specificData.getClass(resolvedFieldSchema), (GenericRecord) value, specificData); + value = convertToSpecificRecord(getSpecificRecordClass(resolvedFieldSchema, specificData), (GenericRecord) value, specificData); break; case ARRAY: value = ((List<?>) value).stream().map(element -> convertFieldToSpecificRecordValue(resolvedFieldSchema.getElementType(), element, specificData)).collect(Collectors.toList());
