apple/pkl · error · VmException
cannotRenderSubtypeForProtobuf
cannotRenderSubtypeForProtobuf
Error message
cannotRenderSubtypeForProtobuf
What it means
When rendering Pkl values to Protobuf, the renderer walks a class hierarchy and refuses if it encounters a value whose concrete class is a non-final subclass of the declared class being rendered. Protobuf rendering requires the exact class, so a value of a subtype that differs from the target class cannot be represented. The error names both the offending subtype and the expected class.
Source
Thrown at pkl-core/src/main/java/org/pkl/core/stdlib/protobuf/RendererNodes.java:406
writePropertyName();
expectedName = Identifier.get("it_" + computedName);
propertyPath.push(expectedName);
startMessage();
} else if (type instanceof ListingOrMappingTypeNode listingOrMappingType) {
hasCollection = true;
collectionType = listingOrMappingType.getValueTypeNode();
} else if (type instanceof ListTypeNode listType) {
hasCollection = true;
collectionType = listType.getElementTypeNode();
} else if (type instanceof MapTypeNode mapType) {
hasCollection = true;
collectionType = mapType.getValueTypeNode();
} else if (type instanceof SetTypeNode setType) {
hasCollection = true;
collectionType = setType.getElementTypeNode();
} else if (type instanceof NonFinalClassTypeNode) {
if (type.getVmClass() != clazz) {
throw new VmExceptionBuilder()
.evalError("cannotRenderSubtypeForProtobuf", type.getVmClass(), clazz)
.build();
}
}
}
}
visit(value);
if (hasWrapper) {
endMessage();
var popped = propertyPath.pop();
assert popped == expectedName : "Corrupted traversal stack.";
} else if (hasCollection) {
collectionType = prevCollectionType;
}
var popped = propertyPath.pop();
assert name == popped : "Corrupted traversal stack.";
}
View on GitHub (pinned to f3efcbfc9b)
Solutions
- Change the property's declared type to the exact concrete class of the value so subtype and rendered class match
- Use Protobuf `oneof`/separate properties per subtype instead of a polymorphic base-typed property
- Choose a different output format (e.g. JSON/YAML/PBF) that supports subtyping via a converter
Example fix
// before
worker: Worker = new SshWorker { host = "h" } // SshWorker != Worker for Protobuf
// after
worker: SshWorker = new SshWorker { host = "h" } Defensive patterns
Strategy: type-guard
Validate before calling
// before rendering to protobuf, check the value's class equals the declared property type function isExactClass(value: Any, expected: Class): Boolean = value.getClass() == expected if (!isExactModulePolymorphicValues(module)) throw "polymorphic values cannot be protobuf-rendered"
Type guard
function rendersAsExactClass(value: Any, expected: Class): Boolean = value.getClass() == expected
Prevention
- Avoid base-class-typed properties holding subclass instances in modules destined for Protobuf
- Model polymorphism with oneof-like separate properties or a discriminator + entry Listing
- Prefer final/concrete types for protobuf-rendered fields
- Test rendering modules to protobuf in CI
When it happens
Trigger: Rendering (Protobuf output) a property whose declared type is a non-final class but whose runtime value is an instance of a different subclass of that class, e.g. a property typed as a base class holding a derived-class instance.
Common situations: Config modules using class hierarchies (e.g. a `Worker` base class with `SshWorker`/`K8sWorker` subtypes) rendered to Protobuf; polymorphic collections where elements have mixed concrete types.
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
- Maps containing non-String keys cannot be rendered as JSON.
- Values of type `TypeAlias` cannot be rendered as Properties.
- Values of type `Regex` cannot be rendered as Properties. Val
- The top-level value of a YAML stream must have type `Collect
- Values of type `Duration` cannot be rendered as YAML. Value:
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/4e0e225317fcd817.
Report an issue: GitHub.