Documentation

Documentation versions (currently viewingVaadin 23)
New Acceleration Kits: Observability Kit, SSO Kit, and Swing Kit. Read the blog post.

Kubernetes Kit Session Replication Debug Tool

How to debug HTTP session serialization issues

To help make HTTP sessions fully serializable and deserializable, Kubernetes Kit offers a tool with the purpose of discovering main issues during development.

Tip
Enable serialization extended debug information
When facing exceptions on serialization phase, it may be useful to set the sun.io.serialization.extendedDebugInfo system property to make Java serialization verbose. However, in order to represent the objects being serialized, the toString() method is used, and in rare cases this may cause issues not related to serialization. For example, Hibernate PersistentList.toString() forces initialization of the lazy loaded collection. If this happens without an active Hibernate session, an exception is thrown.

The debug tool is composed by an HTTP session replication tester, engaged by a Vaadin RequestHandler, which tries to serialize and deserialize the HTTP session for every user action on the browser that triggers a server request. It works only on development mode and only if session serialization is enabled by setting the following configuration property:

vaadin.devmode.sessionSerialization.enabled=true

The results from the test are saved in the server logs, and include the following details:

  • Test process outcomes (for example SERIALIZATION_FAILED, DESERIALIZATION_FAILED, SUCCESS, …​);

  • List of unserializable classes;

  • Object class graph in case of deserialization errors; and

  • Potential causes of SerializedLambda ClassCastException

The debug tool is installed automatically on Spring Boot projects. Otherwise, it can be installed manually by configuring the provided VaadinInitListener, either by adding an entry in META-INF/services/VaadinServiceInitListener or by defining a bean if using Vaadin Spring add-on.

com.vaadin.kubernetes.starter.sessiontracker.serialization.SerializationDebugRequestHandler$InitListener
@Configuration
class AppConfig {

    @Bean
    VaadinServiceInitListener serializationDebugInitListener() {
        return new SerializationDebugRequestHandler.InitListener();
    }
}
Tip
For information about Service Init Listener, consult the specific documentation.

Debug Tool Hints Examples

Suppose you have an application that is not coded from the beginning to support session replication. With Kubernetes session replication debug tool in action, issues like the following can be spotted during development mode, by checking server logs.

For views directly or indirectly referencing unserializable objects, the classes causing issues are reported. Double check those classes, make them Serializable and then navigate to the view again to see if the issues are solved or if other problems have arisen.

Session serialization attempt completed in 42 ms with outcomes: [SERIALIZATION_FAILED, NOT_SERIALIZABLE_CLASSES]

NOT SERIALIZABLE CLASSES FOUND:
===============================

com.vaadin.starter.bakery.ui.views.storefront.OrderPresenter
com.vaadin.starter.bakery.ui.utils.converters.LocalTimeConverter
com.vaadin.starter.bakery.backend.service.PickupLocationService
com.vaadin.starter.bakery.ui.views.orderedit.OrderEditor$$Lambda$2202/0x0000000800e2f040
com.vaadin.starter.bakery.backend.service.ProductService
com.vaadin.starter.bakery.ui.views.orderedit.OrderEditor$$Lambda$2165/0x0000000800e1f440
com.vaadin.starter.bakery.backend.service.OrderService$$EnhancerBySpringCGLIB$$75e41bca
com.vaadin.starter.bakery.ui.views.storefront.OrderPresenter$$Lambda$2106/0x0000000800df6440

To solve SerializedLambda class cast exceptions during deserialization, analyze the class graph from the bottom to the top and search for known classes. Check the BEST CANDIDATES sections to identify the failing lambda expression.

Session serialization attempt completed in 22 ms with outcomes: [SERIALIZATION_FAILED, NOT_SERIALIZABLE_CLASSES, DESERIALIZATION_FAILED]

ERRORS DURING SERIALIZATION/DESERIALIZATION PROCESS:
====================================================
DESERIALIZATION_FAILED: cannot assign instance of java.lang.invoke.SerializedLambda to field com.vaadin.flow.component.ComponentEventBus$ListenerWrapper.listener of type com.vaadin.flow.component.ComponentEventListener in instance of com.vaadin.flow.component.ComponentEventBus$ListenerWrapper

DESERIALIZATION STACK. Process failed at depth 52
  class java.util.HashMap [loadFactor (float), threshold (int)]
  class [Ljava.util.Map$Entry; []
    class com.vaadin.flow.spring.SpringVaadinSession [destroyListeners (interface java.util.List)]
      class com.vaadin.flow.server.VaadinSession [cumulativeRequestDuration (long), lastLocked (long), lastRequestDuration (long), lastRequestTimestamp (long), lastUnlocked (long), nextUIId (int), sessionClosedExplicitly (boolean), attributes (class com.vaadin.flow.server.Attributes), browser (class com.vaadin.flow.server.WebBrowser), configuration (interface com.vaadin.flow.function.DeploymentConfiguration), errorHandler (interface com.vaadin.flow.server.ErrorHandler), locale (class java.util.Locale), pushId (class java.lang.String), requestHandlers (class java.util.LinkedList), resourceRegistry (class com.vaadin.flow.server.StreamResourceRegistry), state (class com.vaadin.flow.server.VaadinSessionState)]
      class com.vaadin.flow.server.Attributes [attributes (class java.util.HashMap)]
        class [Ljava.util.Map$Entry; []
          class com.vaadin.flow.spring.scopes.VaadinUIScope$UIStoreWrapper [session (class com.vaadin.flow.server.VaadinSession), sessionDestroyListenerRegistration (interface com.vaadin.flow.shared.Registration), uiStores (interface java.util.Map)]
            class [Ljava.util.Map$Entry; []
              class java.lang.Integer [value (int)]
              class com.vaadin.flow.spring.scopes.BeanStore [destructionCallbacks (interface java.util.Map), objects (interface java.util.Map), session (class com.vaadin.flow.server.VaadinSession)]
                class [Ljava.util.Map$Entry; []
                  class com.vaadin.starter.bakery.ui.dataproviders.OrdersGridDataProvider [defaultSortOrders (interface java.util.List), orderService (class com.vaadin.starter.bakery.backend.service.OrderService), pageObserver (interface java.util.function.Consumer)]
                    class org.vaadin.artur.spring.dataprovider.FilterablePageableDataProvider [filter (class java.lang.Object)]
                    class [Ljava.util.Map$Entry; []
                      class org.vaadin.artur.spring.dataprovider.PageableDataProvider []
                      class com.vaadin.flow.data.provider.DataChangeEvent [unregisterListenerCommand (interface com.vaadin.flow.server.Command)]
                      class java.util.ArrayList [size (int)]
                      class [Ljava.lang.Object; []
                        class com.vaadin.flow.data.provider.AbstractBackEndDataProvider [sortOrders (interface java.util.List)]
                        class java.util.EventObject []
                        class com.vaadin.flow.data.provider.AbstractDataProvider$DataListenerWrapper [listener (interface com.vaadin.flow.function.SerializableConsumer), registration (interface com.vaadin.flow.shared.Registration)]
                          class com.vaadin.flow.data.provider.AbstractDataProvider [listeners (class java.util.HashMap)]
                          class com.vaadin.flow.data.provider.AbstractDataProvider$1 [this$0 (class com.vaadin.flow.data.provider.AbstractDataProvider), val$listener (interface com.vaadin.flow.data.provider.DataProviderListener)]
                            class java.lang.invoke.SerializedLambda [implMethodKind (int), capturedArgs (class [Ljava.lang.Object;), capturingClass (class java.lang.Class), functionalInterfaceClass (class java.lang.String), functionalInterfaceMethodName (class java.lang.String), functionalInterfaceMethodSignature (class java.lang.String), implClass (class java.lang.String), implMethodName (class java.lang.String), implMethodSignature (class java.lang.String), instantiatedMethodType (class java.lang.String)]
                              class [Ljava.lang.Object; []
                              class [Ljava.lang.Object; []
                                class com.vaadin.flow.data.provider.DataCommunicator [activeStart (int), assumeEmptyClient (boolean), assumedSize (int), definedSize (boolean), fetchEnabled (boolean), itemCountEstimate (int), itemCountEstimateIncrease (int), lastSent (int), nextUpdateId (int), pageSize (int), pagingEnabled (boolean), resendEntireRange (boolean), sizeReset (boolean), skipCountIncreaseUntilReset (boolean), activeKeyOrder (interface java.util.List), arrayUpdater (interface com.vaadin.flow.data.provider.ArrayUpdater), backEndSorting (class java.util.ArrayList), confirmedUpdates (class java.util.HashSet), countCallback (interface com.vaadin.flow.data.provider.CallbackDataProvider$CountCallback), dataGenerator (interface com.vaadin.flow.data.provider.DataGenerator), dataProvider (interface com.vaadin.flow.data.provider.DataProvider), dataProviderUpdateRegistration (interface com.vaadin.flow.shared.Registration), dataUpdater (interface com.vaadin.flow.function.SerializableConsumer), filter (class com.vaadin.flow.data.provider.DataCommunicator$Filter), flushRequest (class com.vaadin.flow.data.provider.DataCommunicator$FlushRequest), flushUpdatedDataRequest (class com.vaadin.flow.data.provider.DataCommunicator$FlushRequest), inMemorySorting (interface com.vaadin.flow.function.SerializableComparator), keyMapper (interface com.vaadin.flow.data.provider.DataKeyMapper), passivatedByUpdate (class java.util.HashMap), requestedRange (class com.vaadin.flow.internal.Range), stateNode (class com.vaadin.flow.internal.StateNode), updatedData (class java.util.HashSet)]
                                  class [Ljava.lang.Object; []
                                  class com.vaadin.flow.component.grid.Grid$GridArrayUpdaterImpl [data (class com.vaadin.flow.component.grid.GridArrayUpdater$UpdateQueueData), this$0 (class com.vaadin.flow.component.grid.Grid), updateQueueFactory (interface com.vaadin.flow.function.SerializableBiFunction)]
                                    class com.vaadin.flow.component.grid.GridArrayUpdater$UpdateQueueData [element (class com.vaadin.flow.dom.Element), hasExpandedItems (interface com.vaadin.flow.function.SerializableSupplier), uniqueKeyProperty (class java.lang.String)]
                                      class com.vaadin.flow.dom.Element []
                                        class com.vaadin.flow.dom.Node [node (class com.vaadin.flow.internal.StateNode), stateProvider (interface com.vaadin.flow.dom.ElementStateProvider)]
                                        class com.vaadin.flow.internal.StateNode [enabled (boolean), hasBeenAttached (boolean), hasBeenDetached (boolean), id (int), isInactiveSelf (boolean), isInitialChanges (boolean), wasAttached (boolean), attachListeners (interface java.util.List), beforeClientResponseEntries (class java.util.ArrayList), changes (interface java.util.Map), detachListeners (interface java.util.List), featureSet (class com.vaadin.flow.internal.StateNode$FeatureSet), features (interface java.io.Serializable), owner (interface com.vaadin.flow.internal.NodeOwner), parent (class com.vaadin.flow.internal.StateNode)]
                                          class [Ljava.lang.Object; []
                                          class [Ljava.lang.Object; []
                                          class com.vaadin.flow.internal.StateNode$FeatureSet [mappings (interface java.util.Map), reportedFeatures (interface java.util.Set)]
                                          class [Lcom.vaadin.flow.internal.nodefeature.NodeFeature; []
                                          class [Lcom.vaadin.flow.internal.nodefeature.NodeFeature; []
                                              class com.vaadin.flow.internal.nodefeature.NodeMap$HashMapValues []
                                              class [Ljava.util.Map$Entry; []
                                              class [Ljava.lang.Object; []
                                                  class [Ljava.lang.Object; []
                                                  class [Lcom.vaadin.flow.internal.nodefeature.NodeFeature; []
                                                  class com.vaadin.flow.internal.StateTree [isRootAttached (boolean), nextBeforeClientResponseIndex (int), nextId (int), dirtyNodes (interface java.util.Set), idToNode (interface java.util.Map), pendingExecutionNodes (interface java.util.Set), rootNode (class com.vaadin.flow.internal.StateNode), uiInternals (class com.vaadin.flow.component.internal.UIInternals)]
                                                    class [Ljava.util.Map$Entry; []
                                                        class [Lcom.vaadin.flow.internal.nodefeature.NodeFeature; []
                                                            class [Ljava.util.Map$Entry; []
                                                            class com.vaadin.flow.component.charts.Chart [changeListener (interface com.vaadin.flow.component.charts.events.internal.ConfigurationChangeListener), configuration (class com.vaadin.flow.component.charts.model.Configuration), configurationUpdateRegistration (interface com.vaadin.flow.shared.Registration), drillCallbackHandler (class com.vaadin.flow.component.charts.Chart$DrillCallbackHandler), drilldownCallback (interface com.vaadin.flow.component.charts.model.DrilldownCallback)]
                                                                class [Ljava.util.Map$Entry; []
                                                                  class com.vaadin.flow.component.charts.events.ChartLoadEvent []
                                                                  class [Ljava.lang.Object; []
                                                                        class com.vaadin.flow.component.ComponentEventBus$$Lambda$925/0x000000080070a040 [arg$1 (class com.vaadin.flow.component.ComponentEventBus), arg$2 (class java.lang.Class), arg$3 (class com.vaadin.flow.component.ComponentEventBus$ListenerWrapper)]
                                                                        class [Ljava.lang.Object; []
                                                                                class [Lcom.vaadin.flow.internal.nodefeature.NodeFeature; []
                                                                                    class [Ljava.lang.Object; []
                                                                                        class [Lcom.vaadin.flow.internal.nodefeature.NodeFeature; []
                                                                                            class [Ljava.util.Map$Entry; []
                                                                                                class [Ljava.util.Map$Entry; []
                                                                                                  class [Ljava.lang.Object; []
                                                                                                        class com.vaadin.flow.component.ComponentEventBus$$Lambda$925/0x000000080070a040 [arg$1 (class com.vaadin.flow.component.ComponentEventBus), arg$2 (class java.lang.Class), arg$3 (class com.vaadin.flow.component.ComponentEventBus$ListenerWrapper)]


SERIALIZED LAMBDA CLASS CAST EXCEPTION BEST CANDIDATES:
=======================================================
	[capturingClass=com/vaadin/flow/component/grid/AbstractColumn, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/flow/component/grid/AbstractColumn.lambda$new$553b070$1:(Lcom/vaadin/flow/component/AttachEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/AttachEvent;)V, numCaptured=1]
	[capturingClass=com/vaadin/flow/component/grid/Grid, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/flow/component/grid/Grid.lambda$addColumn$4f76937c$1:(Lcom/vaadin/flow/component/grid/Grid$Column;Ljava/lang/String;Lcom/vaadin/flow/component/AttachEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/AttachEvent;)V, numCaptured=3]
	[capturingClass=com/vaadin/flow/component/grid/Grid, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/flow/component/grid/Grid.onDragStart:(Lcom/vaadin/flow/component/grid/dnd/GridDragStartEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/grid/dnd/GridDragStartEvent;)V, numCaptured=1]
	[capturingClass=com/vaadin/flow/component/grid/Grid, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/flow/component/grid/Grid.onDragEnd:(Lcom/vaadin/flow/component/grid/dnd/GridDragEndEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/grid/dnd/GridDragEndEvent;)V, numCaptured=1]
	[capturingClass=com/vaadin/starter/bakery/ui/views/dashboard/DashboardView, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/starter/bakery/ui/views/dashboard/DashboardView.lambda$measurePageLoadPerformance$387549c5$1:(Ljava/util/concurrent/atomic/AtomicInteger;Lcom/vaadin/flow/component/charts/events/ChartLoadEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/charts/events/ChartLoadEvent;)V, numCaptured=2]
	[capturingClass=com/vaadin/starter/bakery/ui/MainView, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/starter/bakery/ui/MainView.lambda$init$9b1b5227$2:(Lcom/vaadin/flow/component/ClickEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/ClickEvent;)V, numCaptured=0]
	[capturingClass=com/vaadin/flow/component/button/Button, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/flow/component/button/Button.lambda$new$e38dae27$1:(Lcom/vaadin/flow/component/ClickEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/ClickEvent;)V, numCaptured=1]
	[capturingClass=com/vaadin/starter/bakery/ui/MainView, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/starter/bakery/ui/MainView.lambda$init$9b1b5227$1:(Lcom/vaadin/flow/component/tabs/Tabs$SelectedChangeEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/tabs/Tabs$SelectedChangeEvent;)V, numCaptured=0]
	[capturingClass=com/vaadin/flow/component/login/AbstractLogin, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/flow/component/login/AbstractLogin.lambda$new$9b1b5227$1:(Lcom/vaadin/flow/component/login/AbstractLogin$LoginEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/login/AbstractLogin$LoginEvent;)V, numCaptured=1]

SERIALIZED LAMBDA CLASS CAST EXCEPTION ALL DETECTED TARGETS:
============================================================
	[capturingClass=com/vaadin/flow/data/provider/DataCommunicator, functionalInterfaceClass=com/vaadin/flow/data/provider/DataProviderListener, functionalInterfaceMethod=onDataChange:(Lcom/vaadin/flow/data/provider/DataChangeEvent;)V, implementation=com/vaadin/flow/data/provider/DataCommunicator.lambda$handleAttach$425c8a01$1:(Lcom/vaadin/flow/data/provider/DataChangeEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/data/provider/DataChangeEvent;)V, numCaptured=1]
    [ .... omitted for brevity .... ]

For example, on the above snippet when navigating to the application DashboardView, the process failed to deserialize a lambda expression into com.vaadin.flow.component.ComponentEventListener.

DESERIALIZATION_FAILED: cannot assign instance of java.lang.invoke.SerializedLambda to field com.vaadin.flow.component.ComponentEventBus$ListenerWrapper.listener of type com.vaadin.flow.component.ComponentEventListener in instance of com.vaadin.flow.component.ComponentEventBus$ListenerWrapper

From the class graph it can be noticed that the failing lambda is a is a com.vaadin.flow.component.charts.Chart listener for the CharLoadEvent

class com.vaadin.flow.component.charts.Chart [changeListener (interface com.vaadin.flow.component.charts.events.internal.ConfigurationChangeListener), configuration (class com.vaadin.flow.component.charts.model.Configuration), configurationUpdateRegistration (interface com.vaadin.flow.shared.Registration), drillCallbackHandler (class com.vaadin.flow.component.charts.Chart$DrillCallbackHandler), drilldownCallback (interface com.vaadin.flow.component.charts.model.DrilldownCallback)]
        class com.vaadin.flow.component.charts.events.ChartLoadEvent []
            class com.vaadin.flow.component.ComponentEventBus$$Lambda$925/0x000000080070a040 [arg$1 (class com.vaadin.flow.component.ComponentEventBus), arg$2 (class java.lang.Class), arg$3 (class com.vaadin.flow.component.ComponentEventBus$ListenerWrapper)]
                    nodefeature.NodeFeature; []
                        class com.vaadin.flow.component.ComponentEventBus$$Lambda$925/0x000000080070a040 [arg$1 (class com.vaadin.flow.component.ComponentEventBus), arg$2 (class java.lang.Class), arg$3 (class com.vaadin.flow.component.ComponentEventBus$ListenerWrapper)]

Checking the BEST CANDIDATES section, it is possible to see that there is an entry whose implementation attribute mentions a lambda expression defined in DashboardView that captures a ChartLoadEvent. Furthermore, the method defining the lambda expression can be detected from the implementation attribute.

In the example, the value is as follows:

implementation=com/vaadin/starter/bakery/ui/views/dashboard/DashboardView.lambda$measurePageLoadPerformance$387549c5$1:(Ljava/util/concurrent/atomic/AtomicInteger;Lcom/vaadin/flow/component/charts/events/ChartLoadEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/charts/events/ChartLoadEvent;)V`

It indicates that the method is measurePageLoadPerformance in DashboardView.

SERIALIZED LAMBDA CLASS CAST EXCEPTION BEST CANDIDATES:
=======================================================
    [ .... omitted .... ]
	[capturingClass=com/vaadin/starter/bakery/ui/views/dashboard/DashboardView, functionalInterfaceClass=com/vaadin/flow/component/ComponentEventListener, functionalInterfaceMethod=onComponentEvent:(Lcom/vaadin/flow/component/ComponentEvent;)V, implementation=com/vaadin/starter/bakery/ui/views/dashboard/DashboardView.lambda$measurePageLoadPerformance$387549c5$1:(Ljava/util/concurrent/atomic/AtomicInteger;Lcom/vaadin/flow/component/charts/events/ChartLoadEvent;)V, instantiatedMethodType=(Lcom/vaadin/flow/component/charts/events/ChartLoadEvent;)V, numCaptured=2]
    [ .... omitted .... ]

The mentioned method presents the following code, and it can be deduced that the problem is that the lambda expression is capturing this and providing it to other components that probably store a reference to it.

	private void measurePageLoadPerformance() {
		final int nTotal = 5; // the total number of charts on the page
		AtomicInteger nLoaded = new AtomicInteger();
		ComponentEventListener<ChartLoadEvent> chartLoadListener = (event) -> {
			nLoaded.addAndGet(1);
			if (nLoaded.get() == nTotal) {
				UI.getCurrent().getPage().executeJs("$0._chartsLoadedResolve()", this); 1
			}
		};

		todayCountChart.addChartLoadListener(chartLoadListener); 2
		deliveriesThisMonthChart.addChartLoadListener(chartLoadListener);
		deliveriesThisYearChart.addChartLoadListener(chartLoadListener);
		yearlySalesGraph.addChartLoadListener(chartLoadListener);
		monthlyProductSplit.addChartLoadListener(chartLoadListener);
	}
  1. Lambda expression captures this instance

  2. Lambda expression is used as ChartLoadListener

To fix this issue, replace the lambda expression with an anonymous class.

ComponentEventListener<ChartLoadEvent> chartLoadListener = new ComponentEventListener<>() {
    @Override
    public void onComponentEvent(ChartLoadEvent event) {
        nLoaded.addAndGet(1);
        if (nLoaded.get() == nTotal) {
            UI.getCurrent().getPage().executeJs("$0._chartsLoadedResolve()", DashboardView.this);
        }
    }
};