Hi Petr,
since you already have an idea what the problem is and how to fix it,
feel free to open a PR with Tests that verify the problem and fix.
There are many FXML Tests already which should help to catch regressions
as well.
More information how to contibute are here:
https://github.com/openjdk/jfx/blob/master/CONTRIBUTING.md
Please note the AI Policy: https://openjdk.org/legal/ai
If something does not work for you, feel free to ask here!
-- Marius
Am 12.05.2026 um 16:37 schrieb [email protected]:
Hello,
I would like to report a bug in FXMLLoader that affects any Node subclass
whose
constructor carries a @NamedArg annotation.
PROBLEM
=======
When a class has a @NamedArg-annotated constructor, JavaFXBuilderFactory
returns a
ProxyBuilder for it. Due to how ProxyBuilder intercepts containsKey() and
get(), the
FXML <properties> child element — which the specification explicitly
documents as a
standard read-only map property — fails to load with one of the following
exceptions
depending on the syntax used:
Nested element (<properties><myKey>...</myKey></properties>):
UnsupportedOperationException: Cannot determine type for property.
Attribute (<properties myKey="$ref"/>):
PropertyNotFoundException: Property "myKey" does not exist or is read-
only.
The same FXML works correctly when the class does NOT have a @NamedArg
constructor.
Both the @NamedArg value and the <properties> entry are valid in isolation;
the failure
only occurs when they are combined.
MINIMAL REPRODUCER
==================
A self-contained Maven project is attached (javafx_bug_report.tar.gz).
Requirements: JDK 21+, JavaFX 25 (fetched automatically by Maven), Maven 3.9
+
To reproduce:
tar xzf javafx_bug_report.tar.gz
cd javafx_bug_report
mvn compile exec:exec
Expected output:
Bug: ProxyBuilder breaks <properties> for @NamedArg classes
=============================================================
SimpleButton — no @NamedArg (expected: PASS)
CustomButton — @NamedArg("label") constructor (expected: FAIL)
[ Nested <properties><tag>...</tag></properties> ]
FXML load threw LoadException: ...
Caused by UnsupportedOperationException: Cannot determine type for
property.
[ Attribute <properties tag="$ref"/> ]
FXML load threw LoadException: ...
Caused by PropertyNotFoundException: Property "tag" does not exist
or is read-only.
=============================================================
Nested element approach : FAILED <-- BUG
Attribute approach : FAILED <-- BUG
=============================================================
The project loads two buttons from each FXML:
SimpleButton — no @NamedArg constructor, passes for both syntaxes
CustomButton — @NamedArg("label") constructor present, fails for both
syntaxes
Both the @NamedArg value (label="Test Label") and the <properties> entry are
valid in
isolation; the failure only occurs when they are combined.
ROOT CAUSE
==========
ProxyBuilder overrides containsKey() and get() via getTemporaryContainer(),
which
calls getReadOnlyProperty(). That method was designed to provide a temporary
ArrayListWrapper for read-only List properties (e.g. styleClass, children)
during the
pre-build phase. The problem is that it does so unconditionally, for every
property name:
// ProxyBuilder.java
private Object getReadOnlyProperty(String propName) {
// return ArrayListWrapper now and convert it to proper type later
return new ArrayListWrapper<>(); // <- no check on propName
whatsoever
}
@Override
public boolean containsKey(Object key) {
return (getTemporaryContainer(key.toString()) != null); // ALWAYS
true
}
@Override
public Object get(Object key) {
return getTemporaryContainer(key.toString()); // ALWAYS returns
ArrayListWrapper
}
FXMLLoader's PropertyElement for <properties> calls
parent.getProperties().containsKey("properties") on the ProxyBuilder (which
is a Map),
gets true, then calls get("properties") and receives an ArrayListWrapper. It
then treats
<properties> as a read-only list element rather than the node's
ObservableMap. All
subsequent child property resolution fails against ArrayList instead of
ObservableMap.
RELATION TO EXISTING BUGS
==========================
This is closely related to JDK-8203870 ("ProxyBuilder cannot handle Read-
Only List
Properties properly", open since 2018-05-26), which covers the same
ArrayListWrapper
mechanism breaking read-only List properties. The root cause is identical;
this report
covers the read-only Map case (Node.getProperties()), which has not been
filed before.
Both issues were introduced by the fix for JDK-8134600 (2016), which added
getTemporaryContainer().
SUGGESTED FIX
=============
ProxyBuilder.getReadOnlyProperty(propName) should introspect the target type
and return
new ArrayListWrapper<>() only when the property's getter actually returns a
Collection.
For all other cases (including read-only Map properties like getProperties()
) it should
return null, signalling to FXMLLoader that the property is not a read-only
list container.
private Object getReadOnlyProperty(String propName) {
// Only treat as a read-only list container if the getter actually
returns a Collection
Method getter = findGetter(type, propName);
if (getter != null && Collection.class.isAssignableFrom(getter.
getReturnType())) {
return new ArrayListWrapper<>();
}
return null;
}
This fix would also resolve JDK-8203870 for the list case, since it limits
the
interception to properties that are genuinely read-only collections on the
target type.
The bug report was generated using AI, but the bug itself was found
manually.
Thank you for your time.
Regards Petr Štechmüller