Skip to content

ConsurrentModificationException when a new entry is added into MapSignal #21603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mshabarov opened this issue Jun 3, 2025 · 2 comments · Fixed by #21653
Closed

ConsurrentModificationException when a new entry is added into MapSignal #21603

mshabarov opened this issue Jun 3, 2025 · 2 comments · Fixed by #21653

Comments

@mshabarov
Copy link
Contributor

Description of the bug

This below examples gives me ConcurrentModificationException when I add a new person to the list:

java.util.ConcurrentModificationException: null
	at java.base/java.util.HashMap.forEach(HashMap.java:1433) ~[na:na]
	at com.vaadin.signals.impl.SignalTree.lambda$notifyObservers$5(SignalTree.java:237) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]
	at com.vaadin.signals.impl.SignalTree.runWithLock(SignalTree.java:170) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]
	at com.vaadin.signals.impl.SignalTree.notifyObservers(SignalTree.java:236) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]
	at com.vaadin.signals.impl.SynchronousSignalTree$1.publishChanges(SynchronousSignalTree.java:73) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]
	at com.vaadin.signals.impl.SignalTree.lambda$commitSingleCommand$6(SignalTree.java:298) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]
	at com.vaadin.signals.impl.SignalTree.runWithLock(SignalTree.java:170) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]
	at com.vaadin.signals.impl.SignalTree.commitSingleCommand(SignalTree.java:294) ~[signals-24.8.0.beta1.jar:24.8.0.beta1]

Expected behavior

I'm not sure whether I use the API wrong or this shouldn't happen. Framework perhaps should spot these cases and show some tip.

Minimal reproducible example

I think this is related use case, when you want to dynamically re-create components based on a state:
```java
View extends VerticalLayout {
    private static final MapSignal<Person> mapSignal =
            SignalFactory.IN_MEMORY_SHARED.map("map", Person.class);
    private VerticalLayout persons = new VerticalLayout();
    static {
        mapSignal.put("Alice", new Person("Alice", 30));
        mapSignal.put("Bob", new Person("Bob", 25));
        mapSignal.put("Charlie", new Person("Charlie", 35));
    }

    public MapSignalView() {
        add(persons);

        Signal.effect(() -> {
            persons.removeAll();
            mapSignal.value().forEach( (key, value) -> {
                var name = new TextField("Name: ");
                name.setValue(value.value().getName());
                name.addValueChangeListener(event ->
                        value.update(person -> {
                            person.setName(event.getValue());
                            return person;
                        }));
                var age = new NumberField("Age: ");
                age.setValue((double) value.value().getAge());
                age.addValueChangeListener(event ->
                        value.update(person -> {
                            person.setAge(event.getValue().intValue());
                            return person;
                        }));
                var remove = new Button("Remove", click -> mapSignal.remove(key));
                persons.add(new HorizontalLayout(name, age, remove));
            });
        });

        var newPerson = new TextField("Add new person", event -> {
            String name = event.getValue();
            if (!name.isEmpty() && !mapSignal.value().containsKey(name)) {
                mapSignal.put(name, new Person(name, 0));
                event.getSource().setValue("");
            }
        });

        add(newPerson);
    }

    public static class Person implements Serializable {
        private String name;
        private int age;

        public Person() {
        }

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
}

### Versions

- Vaadin / Flow version: 24.8.0.beta1
@Legioth
Copy link
Member

Legioth commented Jun 5, 2025

Can reproduce. The problem is that a dependency for another child signal is added while reacting to a structural change.

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 24.8.0.beta3 and is also targeting the upcoming stable 24.8.0 version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants