Many answers (and comments) to questions relating to PropertyValueFactory
recommend avoiding that class and others like it. What is wrong with using this class?

- 37,820
- 8
- 53
- 80
-
2This Q&A is meant to be a canonical reference for why we should avoid using PropertyValueFactory. The last few days I've seen many questions where the questioner was using that class and linking to this Q&A would be easier than explaining in the comments why that's a bad idea. I could not find a satisfying existing Q&A that solely discussed this topic. – Slaw May 30 '22 at 17:30
-
1See this related [request for enhancement](https://bugs.openjdk.java.net/browse/JDK-8091088?filter=39543&jql=project%20%3D%20JDK%20AND%20issuetype%20in%20(Bug%2C%20Enhancement)%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20javafx%20AND%20text%20~%20%22deprecate%20propertyvaluefactory%22%20ORDER%20BY%20updated%20DESC) – James_D May 30 '22 at 17:41
-
1Excellent. I added this to the tag info (under "Virtualized Controls") to make it easy to find. – James_D May 30 '22 at 17:52
1 Answers
TL;DR:
You should avoid
PropertyValueFactory
and similar classes because they rely on reflection and, more importantly, cause you to lose helpful compile-time validations (such as if the property actually exists).Replace uses of
PropertyValueFactory
with lambda expressions. For example, replace:nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
With:
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
(assumes you're using Java 8+ and you've defined the model class to expose JavaFX properties)
PropertyValueFactory
This class, and others like it, is a convenience class. JavaFX was released during the era of Java 7 (if not earlier). At that time, lambda expressions were not part of the language. This meant JavaFX application developers had to create an anonymous class whenever they wanted to set the cellValueFactory
of a TableColumn
. It would look something like this:
// Where 'nameColumn' is a TableColumn<Person, String> and Person has a "name" property
nameColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person> data) {
return data.getValue().nameProperty();
}
});
As you can see, this is pretty verbose. Imagine doing the same thing for 5 columns, 10 columns, or more. So, the developers of JavaFX added convenience classes such as PropertyValueFactory
, allowing the above to be replaced with:
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
Disadvantages of PropertyValueFactory
However, using PropertyValueFactory
and similar classes has its own disadvantages. Those disadvantages being:
- Relying on reflection, and
- Losing compile-time validations.
Reflection
This is the more minor of the two disadvantages, though it directly leads to the second one.
The PropertyValueFactory
takes the name of the property as a String
. The only way it can then invoke the methods of the model class is via reflection. You should avoid relying on reflection when you can, as it adds a layer of indirection and slows things down (though in this case, the performance hit is likely negligible).
The use of reflection also means you have to rely on conventions not enforceable by the compiler. In this case, if you do not follow the naming conventions for JavaFX properties exactly, then the implementation will fail to find the needed methods, even when you think they exist.
Reflection also requires opening packages to loosen security in a modular application, otherwise you will receive error messages such as this:
java.lang.RuntimeException: java.lang.IllegalAccessException: module javafx.base cannot access class application.Item (in module ProjectReviewerCollection) because module ProjectReviewerCollection does not open application to javafx.base
No Compile-time Validations
Since PropertyValueFactory
relies on reflection, Java can only validate certain things at run-time. More specifically, the compiler cannot validate that the property exists, or if the property is the right type, during compilation. This makes developing the code harder.
Say you had the following model class:
/*
* NOTE: This class is *structurally* correct, but the method names
* are purposefully incorrect in order to demonstrate the
* disadvantages of PropertyValueFactory. For the correct
* method names, see the code comments above the methods.
*/
public class Person {
private final StringProperty name = new SimpleStringProperty(this, "name");
// Should be named "setName" to follow JavaFX property naming conventions
public final void setname(String name) {
this.name.set(name);
}
// Should be named "getName" to follow JavaFX property naming conventions
public final String getname() {
return name.get();
}
// Should be named "nameProperty" to follow JavaFX property naming conventions
public final StringProperty nameproperty() {
return name;
}
}
Having something like this would compile just fine:
TableColumn<Person, Integer> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(tc -> new TableCell<>() {
@Override
public void updateItem(Integer item, boolean empty) {
if (empty || item == null) {
setText(null);
} else {
setText(item.toString());
}
}
});
But there will be two issues at run-time.
The
PropertyValueFactory
won't be able to find the "name" property and will throw an exception at run-time. This is because the methods ofPerson
do not follow the property naming conventions. In this case, they failed to follow thecamelCase
pattern. The methods should be:getname
→getName
setname
→setName
nameproperty
→nameProperty
Fixing this problem will fix this error, but then you run into the second issue.
The call to
updateItem(Integer item, boolean empty)
will throw aClassCastException
, saying aString
cannot be cast to anInteger
. We've "accidentally" (in this contrived example) created aTableColumn<Person, Integer>
when we should have created aTableColumn<Person, String>
.
What Should You Use Instead?
You should replace uses of PropertyValueFactory
with lambda expressions, which were added to the Java language in version 8.
Since Callback
is a functional interface, it can be used as the target of a lambda expression. This allows you to write this:
// Where 'nameColumn' is a TableColumn<Person, String> and Person has a "name" property
nameColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person> data) {
return data.getValue().nameProperty();
}
});
As this:
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
Which is basically as concise as the PropertyValueFactory
approach, but with neither of the disadvantages discussed above. For instance, if you forgot to define Person#nameProperty()
, or if it did not return an ObservableValue<String>
, then the error would be detected at compile-time. This forces you to fix the problem before your application can run.
The lambda expression even gives you more freedom, such as being able to use expression bindings.
Disadvantage
There is a couple of disadvantages, though they are both small.
The "number properties", such as
IntegerProperty
andDoubleProperty
, all implementObservableValue<Number>
. This means you either have to:Use
Number
instead of e.g.,Integer
as the column's value type. This is not too bad, since you can call e.g.,Number#intValue()
if and as needed.Or use e.g.,
IntegerProperty#asObject()
, which returns anObjectProperty<Integer>
. The other "number properties" have a similar method.
column.setCellValueFactory(data -> data.getValue().someIntegerProperty().asObject());
The implementation of the
Callback
cannot be defined in FXML. By contrast, aPropertyValueFactory
can be declared in FXML.
Kotlin
If you're using Kotlin, then the lambda may look something like this:
nameColumn.setCellValueFactory { it.value.nameProperty }
Assuming you defined the appropriate Kotlin properties in the model class. See this Stack Overflow answer for details.
Records
If the data is your TableView is read-only then you can use a record, which is a special kind of class.
For a record, you cannot use a PropertyValueFactory
and must use a custom cell value factory (e.g. a lambda).
The naming strategy for record accessor methods differs from the standard java beans naming strategy. For example, for a member called name
the standard java beans accessor name used by PropertyValueFactory
would be getName()
, but for a record, the accessor for the name
member is just name()
. Because records don't follow the naming conventions required by a PropertyValueFactory
, a PropertyValueFactory
cannot be used to access data stored in records.
However, the lambda approach detailed in this answer will be able to access the data in the record just fine.
Further information and an example of using a record with a cell value factory for a TableView can be found at: