> Markdown version of [Shared Signals](https://vaadin.com/docs/next/flow/ui-state/shared-signals). Section index: [llms.txt](https://vaadin.com/docs/next/flow/llms.txt)

# Shared Signals

Shared signals provide thread-safe, transactional state management for scenarios where state needs to be synchronized across multiple users or sessions. They are ideal for collaborative features, real-time dashboards, and any state that multiple users need to access concurrently.

All shared signals use an underlying JSON data representation that is replaced on modification. When reading a value, a new instance is created from the internal JSON representation, so the value object is never modified directly or concurrently. This immutability ensures thread-safe access without explicit synchronization.

## <a id="shared-signal-types"></a>Shared Signal Types

Several shared signal types are available for different use cases.

| Signal Type            | Description                                                  |
| ---------------------- | ------------------------------------------------------------ |
| `SharedValueSignal<T>` | Single value of any type, updated atomically                 |
| `SharedNumberSignal`   | Numeric value with atomic increment/decrement operations     |
| `SharedListSignal<T>`  | Ordered list with per-item change tracking                   |
| `SharedMapSignal<T>`   | Key-value map with string keys and per-entry change tracking |
| `SharedNodeSignal`     | Tree structure combining value, list, and map capabilities   |

### <a id="sharedvaluesignal"></a>SharedValueSignal

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

```java
import com.vaadin.flow.signals.shared.SharedValueSignal;

SharedValueSignal<String> name = new SharedValueSignal<>(String.class);
name.set("John Doe"); // Set the value
String currentName = name.peek(); // Get the value outside reactive contexts
```

### <a id="sharednumbersignal"></a>SharedNumberSignal

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`.

```java
import com.vaadin.flow.signals.shared.SharedNumberSignal;

SharedNumberSignal counter = new SharedNumberSignal();
counter.set(5); // Set the value
counter.incrementBy(1); // Increment by 1
counter.incrementBy(-2); // Decrement by 2
int count = counter.getAsInt(); // Get the value as int
```

### <a id="sharedlistsignal"></a>SharedListSignal

A signal containing a list of values. Each value in the list is accessed as a separate `SharedValueSignal`. This is different from `SharedValueSignal<List<T>>` - a `SharedListSignal` tracks both structural changes (additions, removals, reordering) and individual item changes, while `SharedValueSignal<List<T>>` only notifies when the entire list is replaced.

```java
import com.vaadin.flow.signals.shared.SharedListSignal;

SharedListSignal<Person> persons = new SharedListSignal<>(Person.class);
persons.insertFirst(new Person("Jane", 25)); // Add to the beginning
persons.insertLast(new Person("John", 30)); // Add to the end
List<SharedValueSignal<Person>> personList = persons.peek(); // Get all persons
personList.get(0).set(new Person("Alice", 22)); // Update the value of a child signal
```

#### <a id="positioning-items-with-listposition"></a>Positioning Items with ListPosition

`SharedListSignal` uses `ListPosition` to specify where items should be inserted. The `insertFirst()` and `insertLast()` methods are convenience shortcuts for the most common cases. For precise positioning, use `insertAt()` with a `ListPosition`:

```java
import com.vaadin.flow.signals.shared.SharedListSignal.ListPosition;

SharedListSignal<String> items = new SharedListSignal<>(String.class);
SharedValueSignal<String> first = items.insertFirst("First").signal();
SharedValueSignal<String> last = items.insertLast("Last").signal();

// Insert after a specific entry
items.insertAt("After First", ListPosition.after(first));

// Insert before a specific entry
items.insertAt("Before Last", ListPosition.before(last));

// Insert between two adjacent entries (requires the two to be adjacent)
items.insertAt("Between", ListPosition.between(first, last));

// Equivalent to insertFirst / insertLast
items.insertAt("New First", ListPosition.first());
items.insertAt("New Last", ListPosition.last());
```

#### <a id="inserting-multiple-items"></a>Inserting Multiple Items (since V25.2)

Use `insertAllFirst()`, `insertAllLast()`, or `insertAllAt()` to insert several items in one operation. All inserts are performed within a single transaction, so other users see the entire batch as one atomic change and only one synchronization is needed:

```java
import com.vaadin.flow.signals.operations.BulkInsertOperation;

SharedListSignal<String> items = new SharedListSignal<>(String.class);

// Insert at the end, preserving the collection order
BulkInsertOperation<SharedValueSignal<String>> operation =
    items.insertAllLast(List.of("A", "B", "C"));

// Insert at the beginning
items.insertAllFirst(List.of("First", "Second"));

// Insert at a specific position
items.insertAllAt(List.of("X", "Y"),
    ListPosition.after(operation.signals().get(0)));
```

Each method returns a `BulkInsertOperation` that provides the signals for the inserted entries through `signals()` and tracks the entire batch with a single result future through `result()`. The signals can be used immediately, even before the result of the operation is confirmed.

#### <a id="reordering-items-with-moveto"></a>Reordering Items with moveTo

Use `moveTo()` to change the position of an existing list entry without removing and re-inserting it:

```java
SharedListSignal<String> items = new SharedListSignal<>(String.class);
SharedValueSignal<String> a = items.insertLast("A").signal();
SharedValueSignal<String> b = items.insertLast("B").signal();
SharedValueSignal<String> c = items.insertLast("C").signal();

// Move C to the beginning
items.moveTo(c, ListPosition.first());
// List is now: ["C", "A", "B"]

// Move A after B
items.moveTo(a, ListPosition.after(b));
// List is now: ["C", "B", "A"]
```

### <a id="sharedmapsignal"></a>SharedMapSignal

A signal containing a map of values with string keys (keys are always `String` type). Each value in the map is accessed as a separate `SharedValueSignal`.

```java
import com.vaadin.flow.signals.shared.SharedMapSignal;

SharedMapSignal<String> properties = new SharedMapSignal<>(String.class);
properties.put("name", "John"); // Add or update a property
properties.putIfAbsent("age", "30"); // Add only if not present
SharedValueSignal<String> nameSignal = properties.peek().get("name"); // Get signal for a key
Map<String, SharedValueSignal<String>> propertyMap = properties.peek(); // Get all properties
properties.remove("age"); // Remove an entry
```

### <a id="sharednodesignal"></a>SharedNodeSignal

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.

```java
import com.vaadin.flow.signals.shared.SharedNodeSignal;

SharedNodeSignal user = new SharedNodeSignal();
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.peek().mapChildren().get("name").asValue(String.class).peek(); // Access 'John Doe'
user.peek().mapChildren().get("age").asValue(Integer.class).peek(); // Access 30
user.peek().listChildren().getLast().asValue(String.class).peek(); // Access 'Reading'

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

### <a id="parameterized-value-types"></a>Parameterized Value Types (since V25.3)

The `Class` object the examples above pass can’t express type arguments, so `new SharedValueSignal<>(Set.class)` loses the element type and the value can’t be read back as a `Set<Role>`. For a parameterized type, pass a Jackson `TypeReference` instead of a `Class`:

```java
SharedValueSignal<Set<Role>> roles = new SharedValueSignal<>(
        new TypeReference<Set<Role>>() {});

SharedValueSignal<Map<String, List<UUID>>> index = new SharedValueSignal<>(
        Map.of(), new TypeReference<Map<String, List<UUID>>>() {});
```

The same overload is available for the element type of `SharedListSignal` and `SharedMapSignal`, and for the `asValue()`, `asList()`, and `asMap()` methods of `SharedNodeSignal` as well as the `value()` method of its snapshot:

```java
SharedListSignal<List<String>> rows = new SharedListSignal<>(
        new TypeReference<List<String>>() {});

SharedMapSignal<Set<UUID>> groups = new SharedMapSignal<>(
        new TypeReference<Set<UUID>>() {});

Set<UUID> members = user.peek().mapChildren().get("members").peek()
        .value(new TypeReference<Set<UUID>>() {});
```

## <a id="reading-values"></a>Reading Values

### <a id="getting-the-current-value"></a>Getting the Current Value

Shared signals provide two methods for reading values:

- `get()` - reads the value and registers a reactive dependency. This must only be called inside a reactive context such as an effect, a computed signal, or a transaction. Calling `get()` outside a reactive context throws `IllegalStateException`.

- `peek()` - reads the current locally-known value without registering a dependency. Use this for one-time reads in click listeners, initialization code, or any code outside a reactive context.

```java
SharedValueSignal<String> signal = new SharedValueSignal<>(String.class);

// Read current value outside reactive contexts
String current = signal.peek();
```

### <a id="reading-without-registering-dependencies"></a>Reading Without Registering Dependencies

Use `peek()` to read a value without registering a dependency in effects or computed signals:

```java
Signal.effect(component, () -> {
    // peek() doesn't register a dependency
    String peeked = signal.peek();
    // This effect won't re-run when signal changes
});
```

### <a id="getting-confirmed-values-asynchronously"></a>Getting Confirmed Values Asynchronously

The `peekConfirmed()` method returns the last value that has been asynchronously confirmed. This is useful in distributed scenarios where you need to know the definitive server-confirmed state:

```java
SharedValueSignal<String> signal = new SharedValueSignal<>(String.class);

// Get the locally known value (may include optimistic updates)
String optimistic = signal.peek();

// Get only the asynchronously confirmed value
String confirmed = signal.peekConfirmed();
```

Shared signals currently support asynchronously confirmed updates only in single JVM environments but don’t yet provide an implementation for asynchronous updates and confirmations in a cluster environment.

In single JVM deployments, `peekConfirmed()` typically returns the same value as `peek()`. In clustered environments, there may be a brief delay between optimistic local updates and cluster confirmation.

## <a id="writing-values"></a>Writing Values

All shared signals provide atomic write operations:

```java
SharedValueSignal<String> signal = new SharedValueSignal<>(String.class);

// Direct value assignment
signal.set("New value");

// Update based on current value
signal.update(current -> current.toUpperCase());

// Replace only if current value matches expected
signal.replace("expected", "newValue");
```

Writing a value the signal can’t hold throws an `InvalidSignalValueTypeException`. The compiler normally prevents this, but not when the value type has been erased from the generic signature of the writing code — a shared signal handed around as a raw `SharedValueSignal`, for instance. Where a value comes from outside the application, such as a client-side write to a bound element property, the framework catches this and reverts the value instead of propagating the exception; see [Property Binding](https://vaadin.com/docs/next/flow/ui-state/element-bindings.md#property-binding). (since V25.3)

## <a id="thread-safety"></a>Thread Safety

Shared signals are designed for concurrent access from multiple threads and users. All operations are atomic and thread-safe. Signals handle UI updates automatically, so you don’t need to wrap signal operations in `ui.access()`:

```java
// Safe to use from any thread - no ui.access() needed
SharedNumberSignal counter = new SharedNumberSignal();

// Multiple users can safely increment concurrently
counter.incrementBy(1); // Atomic operation, UI updates automatically
```

## <a id="signal-scope-patterns"></a>Signal Scope Patterns

Signals can be composed into beans that use any Vaadin scope annotation, such as `@VaadinSessionScope`, `@VaadinUIScope`, or `@VaadinRouteScope`. The scope annotation determines the lifetime and sharing behavior of the signal holder bean.

### <a id="session-scoped-signals"></a>Session-Scoped Signals

Use `@VaadinSessionScope` to create one signal instance per user session, i.e. shared across all browser tabs and windows for that session.

```java
@Component
@VaadinSessionScope
public class CurrentUserSignal {
    private final ValueSignal<String> usernameSignal;

    public CurrentUserSignal(AuthenticationContext authContext) {
        this.usernameSignal = new ValueSignal<>(
            authContext.getAuthenticatedUser(UserDetails.class)
                .map(UserDetails::getUsername)
                .orElse("anonymous")
        );
    }

    public ValueSignal<String> getUsernameSignal() {
        return usernameSignal;
    }
}
```

**Use case**: Session-specific data like authentication state, preferences, or shopping cart contents.

**Result**: Each HTTP session has its own `CurrentUserSignal` instance, completely separate from other sessions.

### <a id="application-scoped-signals-global"></a>Application-Scoped Signals (Global)

Use `@Component` (singleton) or static fields to create signals shared by all users.

```java
@Component
public class SystemStatusSignal {
    private final ValueSignal<String> statusSignal;

    public SystemStatusSignal() {
        this.statusSignal = new ValueSignal<>("ONLINE");
    }

    public ValueSignal<String> getStatusSignal() {
        return statusSignal;
    }
}
```

**Use case**: Server-wide notifications, global configuration, or shared counters.

**Result**: All users see the same signal value. When it updates, all connected users are notified.

### <a id="view-scoped-signals-per-component-instance"></a>View-Scoped Signals (Per Component Instance)

Declare signals as private instance fields in your view or component class.

```java
@Route("dashboard")
public class DashboardView extends VerticalLayout {
    private final ValueSignal<Integer> counterSignal = new ValueSignal<>(0);

    public DashboardView() {
        Button increment = new Button("Increment");
        increment.addClickListener(e ->
            counterSignal.set(counterSignal.get() + 1));

        Span counter = new Span();
        counter.bindText(counterSignal.map(count -> "Count: " + count));
        add(increment, counter);
    }
}
```

**Use case**: Component-specific UI state that shouldn’t be shared between different instances.

**Result**: Each browser tab or navigation to the view creates a new signal instance.

| Scope           | Declaration                           | Lifetime                                        |
| --------------- | ------------------------------------- | ----------------------------------------------- |
| **Global**      | `@Component` bean or static field     | Application lifetime, shared by all users       |
| **Per Session** | `@Component @VaadinSessionScope` bean | HTTP session lifetime, one per session          |
| **Per View**    | Private instance field                | View instance lifetime, new for each navigation |

## <a id="best-practices"></a>Best Practices

### <a id="use-immutable-values"></a>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.

```java
SharedValueSignal<User> user = new SharedValueSignal<>(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.peek();
u.setAge(u.getAge() + 1); // This won't trigger reactivity!
```

Use Java records for simple data structures:

```java
record Person(String name, int age) {}

SharedValueSignal<Person> person = new SharedValueSignal<>(Person.class);
person.set(new Person("John", 30));

// Update creates a new record
person.update(p -> new Person(p.name(), p.age() + 1));
```

### <a id="storing-signals-as-fields"></a>Storing Signals as Fields

When signals are used in multiple places throughout a class, storing them as fields provides better organization and makes the code easier to understand:

```java
public class MyView extends VerticalLayout {
    // Signals stored as fields for easy access throughout the class
    private final SharedValueSignal<String> nameSignal =
            new SharedValueSignal<>(String.class);

    // Computed signals derived from shared signals
    private final Signal<String> greeting = nameSignal.map(n -> "Hello, " + n);

    public MyView() {
        Span greetingSpan = new Span();
        greetingSpan.bindText(greeting);
        add(greetingSpan);
    }
}
```

## <a id="usage-examples"></a>Usage Examples

For comprehensive UI binding examples including text, visibility, form fields, and dynamic lists, see [Component Bindings](https://vaadin.com/docs/next/flow/ui-state/building-ui.md).

Example 1. Counter Example

```java
public class SharedCounter extends VerticalLayout {
    private final SharedNumberSignal counter = new SharedNumberSignal();

    public SharedCounter() {
        Button button = new Button();
        button.addClickListener(click -> counter.incrementBy(1));
        button.bindText(counter.map(c -> String.format("Clicked %.0f times", c)));
        add(button);
    }
}
```

Example 2. Two-Way Form Field Binding

```java
SharedValueSignal<String> value = new SharedValueSignal<>(String.class);

TextField field = new TextField("Value");
field.bindValue(value, value::set);
// Changes sync both ways: field to signal and signal to field
```

Note that you need to [enable push](https://vaadin.com/docs/next/flow/advanced/server-push.md#push.configuration.enabling) for your application to ensure changes are pushed out for all users immediately when one user makes a change.

## <a id="read-only-signals"></a>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.

```java
SharedValueSignal<String> name = new SharedValueSignal<>(String.class);
SharedValueSignal<String> readOnlyName = name.asReadonly();

// readOnlyName.set("new") would throw an exception
```
