Defining the Text in a JavaFX ComboBox with Objects

Do you know the problem that you have done a specific task already many times in the past, but it’s too long ago to remember exactly how you did it the previous times? One of those cases for me, is how you create a JavaFX ComboBox and configure it to show a specific field of an object in the opened and closed state of the ComboBox.

So to fix the problem, I documented it here in a blog post, and hope I remember that I blogged about it, the next time I need this functionality ;-)

The JavaFX ComboBox control is very useful for selecting a value from a list of options. This works very well with a list of strings, but by default, it doesn’t know how to properly display custom objects. In the first image below, you can see that the toString() method of the object gets used.

Quick Solution: StringConverter

By using a StringConverter for comboBox.setConverter(), we only need a few lines of code to display the text of our choice in both the closed and open state of the ComboBox.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

import java.util.Comparator;

public class ComboBoxWithObjectSimple extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        // Creating the ComboBox
        ComboBox<Person> comboBox = new ComboBox<>();

        // Add data to the ComboBox
        comboBox.getItems().addAll(
                new Person(1, "John", "Doe"),
                new Person(2, "Bob", "Something"),
                new Person(3, "Jane", "Doe"),
                new Person(4, "Jill", "Hello")
        );

        // Define how to display the objects
        comboBox.setConverter(new StringConverter<>() {
            @Override
            public String toString(Person person) {
                return person != null ? person.firstName() + " " + person.lastName() : "";
            }

            @Override
            public Person fromString(String string) {
                // We don't need to handle this for display purposes
                return null;
            }
        });

        // Handle change selection
        comboBox.setOnAction(event -> {
            Person selectedPerson = comboBox.getValue();
            if (selectedPerson != null) {
                System.out.println("Selected person: " + selectedPerson.firstName() + " " + selectedPerson.lastName());
            }
        });

        // Optionally, sort the items based on the last name
        comboBox.getItems().sort(Comparator.comparing(Person::lastName).thenComparing(Person::firstName));

        // Optionally, select the first item in the list
        // Use with care, because setOnAction is not triggered, so you can't select the first item
        comboBox.getSelectionModel().select(0);

        var holder = new VBox(comboBox);
        holder.setPadding(new Insets(10));
        holder.setAlignment(Pos.TOP_CENTER);

        var scene = new Scene(holder, 250, 200);
        stage.setScene(scene);
        stage.setTitle("JavaFX ComboBox Demo");
        stage.setX(25);
        stage.setY(25);
        stage.show();
    }

    // The Java object used in the ComboBox
    public record Person(int id, String firstName, String lastName) {
    }
}

With this approach, both the selected item and the dropdown list items will display the full name of the person.

The source file ComboBoxWithObjectSimple.java is available here.

Advanced Solution: Different Visualization for Closed and Open States

If different information must be shown in the opened state of the ComboBox, we need to add a comboBox.setCellFactory() to specify the content to be shown in the list. In this example, I extended the persons with a color to illustrate you can do more UI styling in the opened view than only defining the text.

public record Person(int id, String firstName, String lastName, Color color) {
}

...

comboBox.getItems().addAll(
        new Person(1, "John", "Doe", Color.RED),
        new Person(2, "Bob", "Something", Color.LIGHTBLUE),
        new Person(3, "Jane", "Doe", Color.LIGHTGREEN),
        new Person(4, "Jill", "Hello", Color.LIGHTGREEN)
);

...

// Define the cell factory for the OPEN state (dropdown items)
comboBox.setCellFactory(p -> new ListCell<>() {
    @Override
    protected void updateItem(Person person, boolean empty) {
        super.updateItem(person, empty);

        if (empty || person == null) {
            setText(null);
        } else {
            // Format with both name and ID for dropdown items
            setText(person.firstName() + " " + person.lastName() + " (ID: " + person.id() + ")");
            // Add background color
            setBackground(new Background(new BackgroundFill(
                    person.color(),
                    CornerRadii.EMPTY,
                    Insets.EMPTY
            )));

        }
    }
});

The full source file ComboBoxWithObjectAdvanced.java is available here.

Conclusion

This same approach can also be used with other JavaFX controls that display items in a list, including ListView and TableView, to display complex data objects in a clear, readable way without the need of complex code.