javagradlejavafxtableviewconcurrentmodification

ConcurrentModificationException when trying to set TableView items from another TableView's selected row


Running into an ConcurrentModificationException when using getSelectionModel().selectedItemProperty() with a listener to get row from historyTableView and using the Purchase item it stores to add information to the purchaseTableView. This exception only arises when selecting any row from historyTableView that is not the last one.

public void refreshStock() {
        historyTableView.setItems(FXCollections.observableList(dao.getPurchaseList()));
        historyTableView.refresh();
    }

    @Override
    @FXML
    public void initialize(URL location, ResourceBundle resources) {
        historyTableView.refresh();
        purchaseTableView.refresh();



// error on line below?
   historyTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            log.debug("Clicked on: " + newValue.toString());
            purchaseTableView.setItems(FXCollections.observableList(newValue.getSoldItemListStream()));

            historyTableView.getSelectionModel().clearSelection();
            purchaseTableView.refresh();
        });
    }

Exception is as follows, thrown when log.debug attempts newValue.toString() (to the best of my understanding)

Exception in thread "JavaFX Application Thread" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1415)
    at java.base/java.util.ArrayList$SubList.size(ArrayList.java:1155)
    at ee.ut.math.tvt.salessystem.dataobjects.Purchase.toString(Purchase.java:66)
    at ee.ut.math.tvt.salessystem.ui.controllers.HistoryController.lambda$initialize$0(HistoryController.java:76)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
    at javafx.base/javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:80)
    at javafx.base/javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:113)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
    at javafx.controls/javafx.scene.control.SelectionModel.setSelectedItem(SelectionModel.java:105)
    at javafx.controls/javafx.scene.control.MultipleSelectionModelBase.lambda$new$0(MultipleSelectionModelBase.java:67)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
        ...

Different listeners, making a Collections.copy() of the DAO list and clearing/refreshing the selection at different moments makes no difference. Mainly I would like to know how this exception is created in a situation such as this and why specifically the last row doesnt throw this exception (clicking on last row changes the purchaseTableView as far as I can tell, this doesnt happen and exception is thrown with other historyTableView rows)


Solution

  • You have some list a. You have created a sublist of this and use this sublist all over the place (specifically, the toString of some a reference to this sublist). You then modify a directly. This invalidates all sublists you made before that modification - any interaction with it, even asking its size(), will get you this exception. Don't hand out sublists of a thing you intend to change. Make copies:

    // do this:
    new ArrayList<>(someList.subList(start, end))
    // not
    someList.subList(start, end)