Docs

Documentation versions (currently viewingVaadin 25 (prerelease))

Introduction

Signals enable reactive state management solution for Vaadin Flow applications. With reactive state management, the state of a view or component is explicitly declared and components are configured to automatically update themselves when the state changes. This helps make UI state management less complicated in general and is particularly useful for sharing state between multiple users in a thread-safe manner. Signals are reactive by design, automatically updating dependent parts of the UI when their values change.

Note
Preview Feature

This is a preview version of Signals. You need to enable it with the feature flag com.vaadin.experimental.flowFullstackSignals. Preview versions may lack some planned features, and breaking changes may be introduced in any Vaadin version. We encourage you to try it out and provide feedback to help us improve it.

Key Features

  • Reactive: Changes to signal values automatically propagate to dependent parts of the UI.

  • Immutable values: Signals work best with immutable values, e.g. String or Java Records, to ensure data consistency.

  • Hierarchical data structures: Signals can represent complex hierarchical data structures.

  • Transactions: Multiple operations can be grouped into a transaction that either succeeds or fails as a whole.

  • Thread-safe by design: Signals are designed to handle concurrent access from multiple users.

  • Atomic operations: Signals support atomic updates to ensure data consistency.

Core Concepts

Signals

A signal is a holder for a value. When the value of a signal value is changed, all dependent parts are automatically updated without the need to manually add and remove change listeners.

Effects

Effects are callbacks that automatically re-run when any signal value they depend on changes. Dependencies are automatically managed based on the signals that were used the last time the callback was run.

Effects are used to update the UI in response to signal changes. The effect is defined in the context of a UI component. The effect is inactive while the component is detached and active while the component is attached.

Source code
Java
ComponentEffect.effect(span, () -> {
    // This code will run whenever any signal value used inside changes
    span.setText(firstNameSignal.value() + " " + lastNameSignal.value());
});

Computed Signals

Computed signals derive their values from other signals. They are automatically updated when any of the signals they depend on change.

Source code
Java
Signal<String> fullName = Signal.computed(() -> {
    return firstNameSignal.value() + " " + lastNameSignal.value();
});

Transactions

Transactions allow grouping multiple signal operations into a single atomic unit. All operations in a transaction either succeed or fail together.

Source code
Java
Signal.runInTransaction(() -> {
    // All operations here will be committed atomically
    firstNameSignal.value("John");
    lastNameSignal.value("Doe");
});

Signal Types

Several signal types are available for different use cases:

ValueSignal

A signal containing a single value. The value is updated as a single atomic change.

Source code
Java
ValueSignal<String> name = new ValueSignal<>(String.class);
name.value("John Doe"); // Set the value
String currentName = name.value(); // Get the value

NumberSignal

A specialized signal for numeric values with support for atomic increments and decrements. The signal value is represented as a double and there are methods to access the value as an int.

Source code
Java
NumberSignal counter = new NumberSignal();
counter.value(5); // Set the value
counter.incrementBy(1); // Increment by 1
counter.incrementBy(-2); // Decrement by 2
int count = counter.valueAsInt(); // Get the value as int

ListSignal

A signal containing a list of values. Each value in the list is accessed as a separate ValueSignal.

Source code
Java
ListSignal<Person> persons = new ListSignal<>(Person.class);
persons.insertFirst(new Person("Jane", 25)); // Add to the beginning
persons.insertLast(new Person("John", 30)); // Add to the end
List<ValueSignal<Person>> personList = persons.value(); // Get all persons
personList.get(0).value(new Person("Bob", 20)); // Update the value of a child signal

MapSignal

A signal containing a map of values with string keys. Each value in the map is accessed as a separate ValueSignal.

Source code
Java
MapSignal<String> properties = new MapSignal<>(String.class);
properties.put("name", "John"); // Add or update a property
properties.putIfAbsent("age", "30"); // Add only if not present
Map<String, ValueSignal<String>> propertyMap = properties.value(); // Get all properties

NodeSignal

A signal representing a node in a tree structure. A node can have its own value and child signals accessed by order or by key. A child node is always either a list child or a map child, but it cannot have both roles at the same time.

Source code
Java
NodeSignal user = new NodeSignal();
user.putChildWithValue("name", "John Doe"); // Add a map child
user.putChildWithValue("age", 30); // Add another map child
user.insertChildWithValue("Reading", ListPosition.last()); // Add a hobby as a list child

user.value().mapChildren().get("name").asValue(String.class).value(); // Access child value 'John Doe'
user.value().mapChildren().get("age").asValue(Integer.class).value(); // Access child value 30
user.value().listChildren().getLast().asValue(String.class).value(); // Access last list child value 'Reading'

MapSignal<String> mapChildren = user.asMap(String.class); // Access all map children
mapChildren.value().get("name"); // Alternative way of accessing child value 'John Doe'

Signal Factory

The SignalFactory interface provides methods for creating signal instances based on a string key, value type and initial value. It supports different strategies for creating instances:

IN_MEMORY_SHARED

Returns the same signal instance for the same name within the same JVM. This is similar to running the respective constructor to initialize a static final field.

Source code
Java
NodeSignal shared = SignalFactory.IN_MEMORY_SHARED.node("myNode");

IN_MEMORY_EXCLUSIVE

Always creates a new instance. Directly running the respective constructor typically leads to clearer code but this factory can be used in cases where the same method supports multiple strategies.

Source code
Java
NodeSignal exclusive = SignalFactory.IN_MEMORY_EXCLUSIVE.node("myNode");

The SignalFactory interface is the extension point for creating custom signal factories. Additional factory implementations are planned for creating signal instances that are shared across multiple JVMs in a cluster.

Usage Examples

Simple Counter Example

This example demonstrates how to bind a counter signal (state) to a button (UI) — the button’s text is updated reactively based on the counter value. The binding between state and UI is done using a ComponentEffect.effect helper. In this case, it creates an effect that uses the value from counter signal to update button text whenever the signal changes.

Source code
Java
public class SimpleCounter extends VerticalLayout {
    // gets a signal instance that is shared across the application
    private final NumberSignal counter =
            SignalFactory.IN_MEMORY_SHARED.number("counter");

    public SimpleCounter() {
        Button button = new Button();
        button.addClickListener(
                // updates the signal value on each button click
                click -> counter.incrementBy(1));
        add(button);

        // Effect that updates the button's text whenever the counter changes
        ComponentEffect.effect(button,
            () -> button.setText(String.format("Clicked %.0f times", counter.value())));
    }
}

Text Field Example

Source code
Java
public class SharedText extends FormLayout {
    private final ValueSignal<String> value =
            SignalFactory.IN_MEMORY_SHARED.value("value", "");

    public SharedText() {
        TextField field = new TextField("Value");

        ComponentEffect.bind(field, value, TextField::setValue);

        field.addValueChangeListener(event -> {
            // Only update signal if value has changed to avoid triggering infinite loop detection
            if (!event.getValue().equals(value.peek())) {
               value.value(event.getValue());
            }
        });

        add(field);
    }
}

ComponentEffect.bind is a helper function that does the same as this explicitly defined effect:

Source code
Java
ComponentEffect.effect(field,
    () -> field.setValue(value.value()));

Note that you need to enable push for your application to ensure changes are pushed out for all users immediately when one user makes a change.

List Example

Source code
Java
public class PersonList extends VerticalLayout {
    private final ListSignal<String> persons =
            SignalFactory.IN_MEMORY_SHARED.list("persons", String.class);

    public PersonList() {
        Button addButton = new Button("Add Person", click -> {
            persons.insertFirst("New person");
        });

        Button updateButton = new Button("Update first Person", click -> {
            ValueSignal<String> first = persons.value().get(0);
            first.update(text -> text + " updated");
        });

        UnorderedList list = new UnorderedList();
        ComponentEffect.effect(list, () -> {
            list.removeAll();
            persons.value().forEach(personSignal -> {
                ListItem li = new ListItem();
                ComponentEffect.bind(li, personSignal, ListItem::setText);
                list.add(li);
            });
        });

        add(addButton, updateButton, list);
    }
}

Removing all list items and creating them again is not the most efficient solution. A helper method will be added later to bind child components in a more efficient way.

The effect that creates new list item components will be run only when a new item is added to the list but not when the value of an existing item is updated.

Best Practices

Use Immutable Values

Signals work best with immutable values. This ensures that changes to signal values are always made through the signal API, which maintains consistency and reactivity.

Source code
Java
ValueSignal<User> user = new ValueSignal<>(User.class);
// Good: Creating a new immutable object
user.update(u -> new User(u.getName(), u.getAge() + 1));

// Bad: Modifying the object directly
User u = user.value();
u.setAge(u.getAge() + 1); // This won't trigger reactivity!

Use Component Effects for UI Updates

Various helper methods simplify binding of signals to components:

Source code
Java
// Bind an effect function to a component:
ComponentEffect.effect(myComponent, () -> {
  Notification.show("Component is attached and signal value is " + someSignal.value());
});

// Bind an effect function to a component using a value from a give signal:
ComponentEffect.bind(label, user.map(u -> u.getName()), Span::setText);
ComponentEffect.bind(label, stringSignal, Span::setText);
ComponentEffect.bind(label, stringSignal.map(value -> !value.isEmpty()), Span::setVisible);

Use Transactions for Atomic Updates

Use transactions when you need to update multiple signals atomically. All changes from the transaction are applied atomically so that no observer can see a partial update. If any change fails, then none of the changes are applied.

Source code
Java
Signal.runInTransaction(() -> {
    firstName.value("John");
    lastName.value("Doe");
    age.value(30);
});

Use update() for Atomic Updates Based on Current Value

Use the update() method when you need to update a signal’s value based on its current value.

Source code
Java
counter.update(current -> current + 1);

Avoid changing signal values from inside effect or computed signal callbacks

Updating the value of a signal as a direct reaction to some other signal value change might cause an infinite loop. To help protect against this, effect and computed signal callbacks are run inside a read-only transaction to prevent any accidental changes.

Whenever possible, you should create a computed signal for any case where the value of some signal affects the value of another signal.

Source code
Java
Signal<String> otherSignal = Signal.computed(() -> {
  return oneSignal.value();
});

If that is not possible and you are certain there’s no risk for infinite loops, you can bypass the check by using the runWithoutTransaction method.

Source code
Java
ComponentEffect.effect(() -> {
  String value = oneSignal.value();

  // This might lead to infinite loops.
  // Do this only if absolutely necessary.
  Signal.runWithoutTransaction(() -> {
    otherSignal.value(value);
  });
});

Advanced Topics

Standalone effects

A standalone signal effect can be used for effects that aren’t related to any UI component.

The effect remains active until explicitly cleaned up. This might lead to memory leaks through any instances referenced by the closure of the effect callback.

Source code
Java
Runnable cleanup = Signal.effect(() -> {
    System.out.println("Counter updated to " + counter.value());
});

// Later, when the effect is no longer needed
cleanup.run();

Signal Mapping

You can transform a signal’s value using the map() method. This is a shorthand for creating a computed signal that depends on exactly one other signal.

Source code
Java
ValueSignal<Integer> age = SignalFactory.IN_MEMORY_SHARED.value("age", Integer.class);
Signal<String> ageCategory = age.map(a ->
    a < 18 ? "Child" : (a < 65 ? "Adult" : "Senior"));

Read-Only Signals

You can create read-only versions of signals that don’t allow modifications. The original signal remains writeable and the read-only instance is also updated for any changes made to the original instance.

Source code
Java
ValueSignal<String> name = SignalFactory.IN_MEMORY_SHARED.value("name", String.class);
ValueSignal<String> readOnlyName = name.asReadonly();

Untracked Signal Access

You can access a signal’s value without registering a dependency, i.e., without triggering reactive effect functions.

Source code
Java
Signal.effect(() -> {
  String name = nameSignal.peek(); // The effect will not depend on nameSignal
});

Signal Binding in Element

Various com.vaadin.flow.dom.Element features support signal binding, such as text, attributes, properties, ClassList, and Style. See Element API for more details of those features.

Each feature works with same rules. When feature is bound to a signal, feature’s value, like text content, is kept synchronized with the signal value while the element is in the attached state. When the element is in detached state, signal value changes have no effect. null signal unbinds the existing binding. While a signal is bound, any attempt to set the value manually in other way than through the signal, throws BindingActiveException. The same happens when trying to bind a new signal while one is already bound.

The following code serves as the base for the examples in the subsequent sections. It adds a button that increments a number signal by one and an empty span element:

Source code
Java
public class SimpleCounter extends VerticalLayout {

    private final NumberSignal counter =
            SignalFactory.IN_MEMORY_SHARED.number("counter");

    public SimpleCounter() {
        Button button = new Button("Increment by one");
        button.addClickListener(
                click -> counter.incrementBy(1));
        add(button);

        Span span = new Span("");
        add(span);
    }
}

Text Binding

Source code
Element#bindText(Signal<String> signal)
// NumberSignal's Double type has to be mapped to String.
Signal<String> signal = counter.map(value -> String.format("Clicked %.0f times", value));

span.getElement().bindText(signal);
// span's text content is now "Clicked 0 times"
Source code
Basic functionality (same rules with all Element bindings)
// The following code demonstrates behavior step by step:
span.getElement().getText(); // returns "Clicked 0 times"
span.getElement().setText(""); // throws BindingActiveException

span.getElement().removeFromParent(); // detaching from the UI
span.getElement().getText(); // returns "Clicked 0 times"
span.getElement().setText(""); // throws BindingActiveException
counter.value(5); // updating the signal value
span.getElement().getText(); // returns "Clicked 0 times"
add(span); // re-attaching the element to the UI
span.getElement().getText(); // returns "Clicked 5 times"

span.getElement().bindText(null); // unbinds the existing binding
span.getElement().getText(); // returns "Clicked 5 times"
span.getElement().setText("");
span.getElement().getText(); // returns ""
counter.value(0);
span.getElement().getText(); // returns ""

Attribute Binding

Source code
Element#bindAttribute(String attribute, Signal<String> signal)
ValueSignal<Boolean> hidden = new ValueSignal<>(false);

span.getElement().bindAttribute("hidden", hidden.map(value -> value ? "" : null));
// DOM has "<span hidden>".

hidden.value(!hidden.peek());
// DOM has "<span>" now.
// Boolean is mapped to "" when true and null when false.
// Some other value like 'foo' would be "<span hidden='foo'>".

Property Binding

Supports various value types: String, Boolean, Double, BaseJsonNode, Object (bean), List and Map.

Typed Lists and Maps are not supported, i.e. the signal must be of type Signal<List<?>> or Signal<Map<?,?>.

Source code
Element#bindProperty(String name, Signal<?> signal)
ValueSignal<Boolean> hidden = new ValueSignal<>(false);

span.getElement().bindProperty("hidden", hidden);
hidden.value(!hidden.peek()); // toggles 'hidden' property
Source code
String type
ValueSignal<String> title = new ValueSignal<>("Hello");
span.getElement().bindProperty("title", title);
title.value("World"); // updates 'title' property
Source code
Double type
NumberSignal width = new NumberSignal();
width.value(100.5);
span.getElement().bindProperty("width", width);
width.incrementBy(50); // updates 'width' property to 150.5
Source code
Object (bean) type
record Person(String name, int age) {
}
ValueSignal<Person> person = new ValueSignal<>(new Person("John", 30));
span.getElement().bindProperty("person", person);
person.value(new Person("Jane", 25));
// element.person is now {name: 'Jane', age: 25}
Source code
List type
ValueSignal<List<String>> items = new ValueSignal<>(List.of("Item 1", "Item 2"));
span.getElement().bindProperty("items", items);
items.value(List.of("Item A", "Item B", "Item C"));
// element.items is now ['Item A', 'Item B', 'Item C']
Source code
Map type
ValueSignal<Map<String, String>> config = new ValueSignal<>(Map.of("key1", "value1"));
span.getElement().bindProperty("config", config);
config.value(Map.of("key1", "value1", "key2", "value2"));
// element.config is now {key1: 'value1', key2: 'value2'}
Source code
BaseJsonNode type
ObjectMapper mapper = new ObjectMapper();
ObjectNode jsonNode = mapper.createObjectNode();
jsonNode.put("key", "value");
ValueSignal<ObjectNode> jsonSignal = new ValueSignal<>(jsonNode);
span.getElement().bindProperty("jsonData", jsonSignal);

ObjectNode updatedJson = mapper.createObjectNode();
updatedJson.put("key", "updatedValue");
updatedJson.put("newKey", "newValue");
jsonSignal.value(updatedJson);
// element.jsonData is now {key: 'updatedValue', newKey: 'newValue'}
Source code
Property change listener for 'change' DOM event
// Adds a property change listener and configures 'hidden' property
// to be synchronized to the server when 'change' DOM event is fired.
span.getElement().addPropertyChangeListener("hidden", "change", event -> {
    Notification.show("'hidden' property changed to: " + event.getValue());
});
// property change event from the client will update the signal value

// Example javascript that dispatches change event from the browser where
// element is <span>:
//   element.hidden = !element.hidden;
//   element.dispatchEvent(new Event('change'));

ClassList Binding

Source code
ClassList#bind(String name, Signal<Boolean> signal)
ValueSignal<Boolean> foo = new ValueSignal<>(false);
ValueSignal<Boolean> bar = new ValueSignal<>(true);

span.getElement().getClassList().bind("foo", foo);
span.getElement().getClassList().bind("bar", bar);
// DOM has "<span class='bar'>"
foo.value(true);
// DOM has "<span class='bar foo'>"

span.getElement().getClassList().clear();
// DOM has "<span class>". Binding is also removed.

Style Binding

Source code
Style#bind(String name, Signal<Boolean> signal)
ValueSignal<String> color = new ValueSignal<>("black");
ValueSignal<String> background = new ValueSignal<>("white");

span.getElement().getStyle().bind("color", color);
span.getElement().getStyle().bind("background", background);
// DOM has "<span style='color: black; background: white'>"

color.value("red");
background.value("gray");
// DOM has "<span style='color: red; background: gray'>"

background.value(""); // same with null
// DOM has "<span style='color: red;'>"

span.getElement().getStyle().clear();
// DOM has "<span style>". Binding is also removed.

SignalPropertySupport helper

Not all component features delegate directly to the state in com.vaadin.flow.dom.Element. For those features, the SignalPropertySupport helper class ensures that state management behaves consistently with other Element bindings.

For example, a component can have a Java API to bind Signal<String> to modify the textContent property of the element in the browser. SignalPropertySupport#create(Component, SerializableConsumer<T>) creates a new instance of SignalPropertySupport with bind(Signal<T>), get(), and set(T) methods. The given consumer sets the textContent based on the value, as shown in the following example.

Source code
MyComponent creation
// The following code demonstrates behavior step by step:
MyComponent component = new MyComponent();
component.bindTextContent(counter.map(v -> "Signal value: " + v));
add(component);
// textContent in browser is "Content: Signal value: 0.0"
component.getTextContent(); // returns "Signal value: 0.0"
component.setTextContent(""); // throws BindingActiveException

component.bindTextContent(null); // unbinds the existing binding
component.setTextContent("");
component.getTextContent(); // returns ""
// textContent in browser is "Content: "
Source code
SignalPropertySupport usage
class MyComponent extends Div {
    private final SignalPropertySupport<String> textProperty =
            SignalPropertySupport.create(this, value -> {
                getElement().executeJs("this.textContent = 'Content: ' + $0", value);
            });

    public String getTextContent() {
        return textProperty.get();
    }

    public void setTextContent(String text) {
        textProperty.set(text);
    }

    public void bindTextContent(Signal<String> textSignal) {
        textProperty.bind(textSignal);
    }
}