Documentation

Documentation versions (currently viewingVaadin 23)

You are viewing documentation for Vaadin 23. View latest documentation

Session Replication

Best practices to achieve session replication.

For a Vaadin application to reach the goals of High Availability and Down Scaling, it is fundamental that the HTTP session is correctly distributed between multiple servers.

If the server processing user requests becomes unavailable, it should be transparently replaced by another, in a way that the user should be able to continue using the application without any disruption.

An offline notification or the loading indicator may appear for an instant, but the browser page is not reloaded. Therefore, work can continue without losing any data.

This means that attributes must be constantly stored in a shared/distributed storage so that newly created HTTP sessions may be filled with the most recent user data.

Kubernetes Kit Session Replication

Kubernetes Kit helps with session replication, by transferring HTTP session data via a back-end session storage (e.g., Hazelcast or Redis) for every request. When a server becomes unavailable, the one taking its place loads user data from the session storage and fills the new server-side HTTP session object.

To transfer information to the session storage, the HTTP session attributes are encoded in a binary format, using Java serialization.

This means that to have session replication working, the application code should be written in way that all objects stored in the HTTP session are eligible for Java serialization, meaning they must implement a special marker interface, java.io.Serializable. Objects that for any reason cannot be serializable (e.g. third party classes) must be defined transient, so they are skipped by the serialization process. Of course, after deserialization, transient fields are null.

For Spring applications, Kubernetes Kit is also able to overcome this limitation. Transient fields referencing Spring beans are detected during serialization and metadata is stored along with the session attributes. On deserialization, transient fields matching metadata are injected with the corresponding Spring bean.

Transient field inspection and injection is performed using Java reflection. For performance reasons and to avoid potential issues with reflection access checks, restrict the inspection to application classes only.

Restrictions can be imposed by configuring vaadin.serialization.transients.include-packages and vaadin.serialization.transients.exclude-packages properties, to limit the inspection to classes whose packages match the configured rules.

Note
You may want to exclude packages not involved in the session.
vaadin.serialization.transients.include-packages=com.example.app.ui,com.example.app.data
vaadin.serialization.transients.exclude-packages=com.example.app.ui.utils

Vaadin and HTTP Session

A Vaadin application consists of UI objects that act as root nodes of graphs of components that represent the web page with which the user interacts through the browser. UIs are collected into a VaadinSession that is stored in the HTTP session, to preserve the server-side state across requests. For this reason, application code should be carefully written to be serializable for session replication to succeed.

Tip
Java serialization documentation
For more information on serialization process, consult Java documentation website.

Session Replication Tips

The aim of the following sections is to give tips and best practices on how to code a fully serializable Vaadin application.

Use Serializable Object as UI Components Members

When writing a component, make sure that all of its non-transient members implement Serializable.

All Vaadin UI components and listeners are already marked as serializable. So if a class extends or implements one of them, it’s not necessary to add explicitly the Serializable implementation statement.

However, it is necessary to do so on non UI components that may be referenced in the UI graph.

For example, if a view is holding a reference to a data transfer object or a configuration object, it must be marked as serializable, and all its members must be serializable.

class ContactForm extends VerticalLayout { 1

    private Product product; 2

}

class Product implements Serializable { 3

    private Category category; 4
}

class Category implements Serializable { 5

}
  1. The ContactForm class extends a Vaadin component that is already serializable.

  2. The ContactForm class has a field of type Product. Therefore, the Product class must also be made serializable.

  3. The Product class is made serializable by extending Serializable.

  4. The Product class has a field of type Category. Therefore, the Category class must also be made serializable.

  5. The Category class is made serializable by extending Serializable.

Transient Fields for Non-Serializable Objects

If a UI component needs to store a reference to an instance of a unserializable class, it must be defined as transient.

In Spring projects, Kubernetes Kit is able to detect these fields and re-inject them after deserialization.

Note
In non Spring project, transient fields should be handled manually either by implementing Java serialization hooks (i.e., writeObject, writeReplace, readObject, readResolve, etc.).
class ContactForm extends VerticalLayout { 1

    private transient ContactService product; 2

}

@Component 4
class ContactService {
    private ContactRepository repository; 3
}
  1. The ContactForm class extends a Vaadin component that is already serializable.

  2. ContactService is not serializable: the field must be defined transient.

  3. ContactService can contain unserializable members since it would not be serialized.

  4. ContactService is a Spring bean: it’s injected after deserialization.

Use Serializable Variants of Functional Interfaces

A component may use Java functional interfaces to allow client code to provide executable code blocks in the form of lambda expressions or method references.

For example, a form view may accept a callback to be executed after data is saved to the database. The callback may be represented by a Consumer<T>, and stored in the onSuccess field.

class ProductForm extends VerticalLayout {

    private Consumer<Product> onSuccess;

}

This breaks serialization process, because Consumer interface is not Serializable. The onSuccess field must be replaced by a serializable friendly type.

To make serialization of ProductForm work, the class can be refactored using a SerializableConsumer<T>

import com.vaadin.flow.function.SerializableConsumer;

class ProductForm extends VerticalLayout {

    private SerializableConsumer<Product> onSuccess;

}

Vaadin offers a serializable-ready version of the most used Java functional interfaces in the com.vaadin.flow.function package.

Take care also when writing utility classes that use functional interfaces as input parameters or return types.

The following class breaks serialization if methods are used:

public class DataProviderUtil {

    1
	public static <S, T> T convertIfNotNull(S source, Function<S, T> converter, Supplier<T> nullValueSupplier) {
 		return source != null ? converter.apply(source) : nullValueSupplier.get();
 	}

    2
	public static <T> ItemLabelGenerator<T> createItemLabelGenerator(Function<T, String> converter) {
 		return item -> convertIfNotNull(item, converter, () -> ""); 3
 	}
}

class OrderEditor {

    private ComboBox<OrderState> status;

    OrderEditor() {
        4
        status.setItemLabelGenerator(
            DataProviderUtil.createItemLabelGenerator(OrderState::getDisplayName)
        );
    }
}
  1. Takes a reference to non-serializable functional interfaces.

  2. Takes a reference to a non-serializable interface.

  3. It captures it into the returned lambda expression.

  4. Stores the ItemLabelGenerator lambda expression in the serializable ComboBox component.

The above utility class must be refactored as follows to use serializable functional interfaces:

public class DataProviderUtil {

	public static <S, T> T convertIfNotNull(S source, SerializableFunction<S, T> converter, SerializableSupplier<T> nullValueSupplier) {
 		return source != null ? converter.apply(source) : nullValueSupplier.get();
 	}

	public static <T> ItemLabelGenerator<T> createItemLabelGenerator(SerializableFunction<T, String> converter) {
 		return item -> convertIfNotNull(item, /* (3) */ converter, () -> "");
 	}
}

Don’t Capture Unserializable Object in Lambdas

When coding component listeners or setting properties that accepts functional interfaces, it’s common to use a lambda expression.

Lambdas can be serialized if the target interface is Serializable, but they must not capture any unserializable objects.

For example, the following code fails during serialization because OrderService is not Serializable.

class OrderEditor {

    private ComboBox<OrderState> status;

    OrderEditor(OrderService service) {
        status.setItemLabelGenerator(item ->
            service.humanReadableState(item)
        );
    }
}

In this case, probably a solution may store the service reference as OrderEditor transient field, accessing the instance in the lambda with a method call (for example, a getter), and implement Java deserialization hooks to somehow inject the service instance.

In Spring projects using Kubernetes Kit, you can rely on transient field handling, and simply add the field for the service instance.

class OrderEditor {

    private transient OrderService service;
    private ComboBox<OrderState> status;

    OrderEditor(OrderService service) {
        this.service = service;
        status.setItemLabelGenerator(item ->
            getOrderService().humanReadableState(item)
        );
    }

    private OrderService getOrderService() {
        return service;
    }
}

Another way to avoid adding the transient field to the main class is to reference the non-serializable object in a serializable proxy that exposes only the required methods.

class OrderEditor {

    private ComboBox<OrderState> status;

    OrderEditor(OrderService service) {
        this.service = service;
        OrderStateLabelGeneratorProxy proxy = new OrderStateLabelGeneratorProxy(service);
        status.setItemLabelGenerator(item ->
            proxy.humanReadableState(item)
        );
    }

    private static class OrderStateLabelGeneratorProxy
        implements Serializable {

        private final transient OrderService service;

        OrderStateLabelGeneratorProxy(OrderService service) {
            this.service = service;
        }

        String humanReadableState(OrderState state) {
            return service.humanReadableState(item);
        }
    }
}

Session Replication Issues

Despite applying the mentioned tips, session replication still may fail because of issues during serialization or deserialization.

Caution
Potential side-effects from serialization when using extended debug info
The sun.io.serialization.extendedDebugInfo system property can be useful in making exception messages more verbose during serialization. The toString() method is used to represent serialized objects, and in rare cases this can cause issues which are not related to serialization. For example, with Hibernate, PersistentList.toString() forces initialization of the lazy loaded collection. If this happens without an active Hibernate session, an exception is thrown.

Common issues with serialization and deserialization are presented in the following section.

SerializedLambda ClassCastException

A common Vaadin application extensively uses lambda expression for components listeners, binder, etc.

When serializing and deserializing lambda expressions, it may happen to face ClassCastException with cryptic messages. For instance, SerializedLambda cannot be cast to class <className> — on serialization — nor can SerializedLambda be assigned to field <fieldName> of type <className> — on deserialization.

Usually, the cause is a "self reference". That is to say, the lambda expression captures an object instance, but the expression is itself a member of the object graph of the captured object.

Detecting the cause of the issues in not easy. In most cases, it requires the developer to intercept the ClassCastException in the IDE debugger and to analyze the call stack to identify the class defining the lambda expression.

Once the lambda expression has been identified, replacing it with an anonymous class may be the solution.

To help make HTTP sessions fully serializable and deserializable, Kubernetes Kit offers a tool whose aim is to discover main issues during development.