Solution: Use StringExpression
All of the values passed to your createTextField
methods are StringExpressions, so you can create a single method that takes a StringExpression as a parameter.
You can write your example method like this:
private void createTextField(
StringExpression property,
int column,
int row
) {
TextField textField = new TextField();
textField.textProperty().bind(property);
form.add(textField, column, row);
}
Using a StringExpression
is preferable to using an ObservableValue
as defined in your updated question.
Explanation
A text field must be bound to an ObservableValue which is a string (type ObservableValue<? extends String>
).
If you don't use generics in the type specification for your parameter and just use a raw type (without the part including <>
), then you lose type safety, so compile-time type checks are not made. For example, the compiler would allow you to pass an Integer-based rather than String-based ObservableValue into the method. If you did that, on execution, you would get a runtime error due to a type mismatch because an Integer cannot be directly treated as a String.
A StringExpression
implements ObservableValue<String>
, so that means that a text field can bind to it directly and type safety at compile time is retained.
Executable example

import javafx.application.Application;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import java.time.Instant;
public class BindSample extends Application {
private final FileModel fileModel = new FileModel(
1, Instant.now(), "test.txt", Instant.now().toEpochMilli()
);
private final GridPane form = new GridPane();
@Override
public void start(Stage stage) {
form.setHgap(5);
form.setVgap(5);
form.setPadding(new Insets(10));
form.addColumn(0,
new Label("Id"),
new Label("Date"),
new Label("FileName"),
new Label(),
new Label(),
new Label("Last Modified")
);
createTextField(fileModel.idProperty().asString(), 1, 0); // ReadOnlyIntegerProperty
createTextField(fileModel.dateProperty().asString(), 1, 1); // ReadOnlyObjectProperty
createTextField(fileModel.fileNameProperty(), 1, 2); // ReadOnlyStringProperty
createTextField(fileModel.lastModifiedProperty().asString(), 1, 5); // ReadOnlyLongProperty
stage.setScene(new Scene(form));
stage.show();
}
private void createTextField(
StringExpression property,
int column,
int row
) {
TextField textField = new TextField();
textField.textProperty().bind(property);
form.add(textField, column, row);
}
public static void main(String[] args) {
launch();
}
private static class FileModel {
private final ReadOnlyIntegerWrapper id;
private final ReadOnlyObjectWrapper<Instant> date;
private final ReadOnlyStringWrapper fileName;
private final ReadOnlyLongWrapper lastModified;
public FileModel(int id, Instant date, String fileName, long lastModified) {
this.id = new ReadOnlyIntegerWrapper(id);
this.date = new ReadOnlyObjectWrapper<>(date);
this.fileName = new ReadOnlyStringWrapper(fileName);
this.lastModified = new ReadOnlyLongWrapper(lastModified);
}
public int getId() {
return id.get();
}
public ReadOnlyIntegerProperty idProperty() {
return id.getReadOnlyProperty();
}
public Instant getDate() {
return date.get();
}
public ReadOnlyObjectProperty<Instant> dateProperty() {
return date.getReadOnlyProperty();
}
public String getFileName() {
return fileName.get();
}
public ReadOnlyStringProperty fileNameProperty() {
return fileName.getReadOnlyProperty();
}
public long getLastModified() {
return lastModified.get();
}
public ReadOnlyLongProperty lastModifiedProperty() {
return lastModified.getReadOnlyProperty();
}
}
}
Minor implementation notes
The example uses ReadOnlyPropertys when demonstrating the binding to match your question sample code. But for this simple example, if the model object were immutable, then a Record without Bindings could be used instead. This can simplify the logic if you know the data never changes.
In the example, I use an Instant
to represent a date as a precise moment in time, but you could use a ZonedDateTime
for better date representation (or LocalDateTime
or LocalDate
if the ambiguity was OK), see Java date representations if you want to understand this.