Skip to content

fix: count resyncs from Flow events so the starter no longer needs spring-web (#415) (CP: 5.0) - #449

Merged
caalador merged 1 commit into
5.0from
cherry/cherry-pick-415-to-5.0
Oct 7, 2026
Merged

caalador merged 1 commit into
5.0from
cherry/cherry-pick-415-to-5.0

Conversation

@caalador

@caalador caalador commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

The vaadin.resync metric is now counted from events that Flow fires, not by a servlet filter that reads UIDL request bodies. The starter no longer needs spring-web or the servlet API, so it compiles again against Flow 25.4. Resyncs over push are now counted too, and a new out_of_sync type is added.

What changed

Breaking: Several public classes are removed:
ResyncDetectionFilter, ResyncDetector and ResyncInspector (micrometer module), and SpringResyncDetectionFilter (spring module). The starter no longer registers the resync FilterRegistrationBean. This only affects apps that used these classes directly or overrode that bean. Everyone else gets the same metric without doing anything.

Behavior change: vaadin.resync now also counts events on push requests. It has a new tag value type=out_of_sync for messages with an id the server did not expect (the user sees the session synchronization error). Dashboards or alerts that expect only resend and resync may see new data.

  • New package-private ResyncMetricsBinder listens for ClientMessageResentEvent, UIResynchronizationEvent and MessageIdSyncErrorEvent on the service event bus. MetricsServiceInitListener registers it when
    vaadin.observability.resync is on (the default). It registers the three counters up front, so each type reports 0 before its first event.
  • Removed the spring-web and jakarta.servlet-api dependencies from the starter. Since spring-web may now be missing at runtime, SpringMetricsServiceInitListener checks for it before enriching the Spring HTTP observation. Without it there is no HTTP observation to enrich, and using the enricher would throw NoClassDefFoundError on the first request.
  • Fixed MetricsCollectorElement: a UI resync detaches and re-attaches every node in place. The element's detach listener then added it to the UI again, which caused an endless loop, so the resync request never finished. Now it only re-adds itself when it is no longer attached. That also covers a collector inside a layout that gets removed from the UI.
  • Updated the README metric and setting descriptions and the architecture diagram.

API Changes

5 classes affected: 4 removed, 1 constant added

com.vaadin.observability.micrometer.MeterNames

// Added
public static final String RESYNC_TYPE_OUT_OF_SYNC = "out_of_sync" // message id the server did not expect

com.vaadin.observability.micrometer.ResyncDetectionFilter

// Removed
public final class ResyncDetectionFilter implements Filter
public ResyncDetectionFilter(MeterRegistry registry)
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

com.vaadin.observability.micrometer.ResyncDetector

// Removed
public final class ResyncDetector
public static final int NO_CLIENT_ID = -1
public enum Kind { NORMAL, RESEND, RESYNC }
public record Result(Kind kind, int lastClientId)
public ResyncDetector(MeterRegistry registry)
public Result inspect(String requestBody, int previousClientId)

com.vaadin.observability.micrometer.ResyncInspector

// Removed
public final class ResyncInspector
public ResyncInspector(MeterRegistry registry)
public static boolean isUidl(HttpServletRequest request)
public static String uiId(HttpServletRequest request)
public void inspect(String body, HttpSession session, String uiId, Object mutex)

com.vaadin.observability.spring.SpringResyncDetectionFilter

// Removed
public final class SpringResyncDetectionFilter extends OncePerRequestFilter
public SpringResyncDetectionFilter(MeterRegistry registry)
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

Test summary

Resync metrics binder:

  • Every type reports 0 before its first event
  • Counts resend, resync and out-of-sync events under their own type
  • Stops counting after the registration is removed
  • Registered only when the resync setting is on

Metrics collector element:

  • Added back to its UI after being removed, directly or with an enclosing layout
  • Not added again after a UI resynchronization

Spring listener:

  • Updates the Spring HTTP observation when spring-web is on the classpath
  • HTTP observation enrichment is skipped, without errors, when spring-web is not on the classpath

Starter (end-to-end):

  • Resend, out-of-sync and resync exported to Prometheus as vaadin_resync_total
  • Not covered: counting over push requests

SpringResyncDetectionFilter.java

Description

Please list all relevant dependencies in this section and provide summary of the change, motivation and context.

Fixes # (issue)

Type of change

  • Bugfix
  • Feature

Checklist

  • I have read the contribution guide: https://vaadin.com/docs/latest/guide/contributing/overview/
  • I have added a description following the guideline.
  • The issue is created in the corresponding repository and I have referenced it.
  • I have added tests to ensure my change is effective and works as intended.
  • New and existing tests are passing locally with my change.
  • I have performed self-review and corrected misspellings.

Additional for Feature type of change

  • Enhancement / new feature was discussed in a corresponding GitHub issue and Acceptance Criteria were created.

…ring-web (#415) (CP: 5.0)

## Summary
The `vaadin.resync` metric is now counted from events that Flow fires,
not by a servlet filter that reads UIDL request bodies. The starter no
longer needs spring-web or the servlet API, so it compiles again against
Flow 25.4. Resyncs over push are now counted too, and a new
`out_of_sync` type is added.

## What changed
**Breaking:** Several public classes are removed:
`ResyncDetectionFilter`, `ResyncDetector` and `ResyncInspector`
(micrometer module), and `SpringResyncDetectionFilter` (spring module).
The starter no longer registers the resync `FilterRegistrationBean`.
This only affects apps that used these classes directly or overrode that
bean. Everyone else gets the same metric without doing anything.

**Behavior change:** `vaadin.resync` now also counts events on push
requests. It has a new tag value `type=out_of_sync` for messages with an
id the server did not expect (the user sees the session synchronization
error). Dashboards or alerts that expect only `resend` and `resync` may
see new data.

- New package-private `ResyncMetricsBinder` listens for
`ClientMessageResentEvent`, `UIResynchronizationEvent` and
`MessageIdSyncErrorEvent` on the service event bus.
`MetricsServiceInitListener` registers it when
`vaadin.observability.resync` is on (the default). It registers the
three counters up front, so each `type` reports 0 before its first
event.
- Removed the `spring-web` and `jakarta.servlet-api` dependencies from
the starter. Since spring-web may now be missing at runtime,
`SpringMetricsServiceInitListener` checks for it before enriching the
Spring HTTP observation. Without it there is no HTTP observation to
enrich, and using the enricher would throw `NoClassDefFoundError` on the
first request.
- Fixed `MetricsCollectorElement`: a UI resync detaches and re-attaches
every node in place. The element's detach listener then added it to the
UI again, which caused an endless loop, so the resync request never
finished. Now it only re-adds itself when it is no longer attached. That
also covers a collector inside a layout that gets removed from the UI.
- Updated the README metric and setting descriptions and the
architecture diagram.

## API Changes
5 classes affected: 4 removed, 1 constant added

### com.vaadin.observability.micrometer.MeterNames

```java
// Added
public static final String RESYNC_TYPE_OUT_OF_SYNC = "out_of_sync" // message id the server did not expect
```

### com.vaadin.observability.micrometer.ResyncDetectionFilter

```java
// Removed
public final class ResyncDetectionFilter implements Filter
public ResyncDetectionFilter(MeterRegistry registry)
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
```

### com.vaadin.observability.micrometer.ResyncDetector

```java
// Removed
public final class ResyncDetector
public static final int NO_CLIENT_ID = -1
public enum Kind { NORMAL, RESEND, RESYNC }
public record Result(Kind kind, int lastClientId)
public ResyncDetector(MeterRegistry registry)
public Result inspect(String requestBody, int previousClientId)
```

### com.vaadin.observability.micrometer.ResyncInspector

```java
// Removed
public final class ResyncInspector
public ResyncInspector(MeterRegistry registry)
public static boolean isUidl(HttpServletRequest request)
public static String uiId(HttpServletRequest request)
public void inspect(String body, HttpSession session, String uiId, Object mutex)
```

### com.vaadin.observability.spring.SpringResyncDetectionFilter

```java
// Removed
public final class SpringResyncDetectionFilter extends OncePerRequestFilter
public SpringResyncDetectionFilter(MeterRegistry registry)
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
```

## Test summary
**Resync metrics binder:**
- Every type reports 0 before its first event
- Counts resend, resync and out-of-sync events under their own type
- Stops counting after the registration is removed
- Registered only when the resync setting is on

**Metrics collector element:**
- Added back to its UI after being removed, directly or with an
enclosing layout
- Not added again after a UI resynchronization

**Spring listener:**
- Updates the Spring HTTP observation when spring-web is on the
classpath
- HTTP observation enrichment is skipped, without errors, when
spring-web is not on the classpath

**Starter (end-to-end):**
- Resend, out-of-sync and resync exported to Prometheus as
`vaadin_resync_total`
- Not covered: counting over push requests

---------

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Artur Signell <artur@vaadin.com>SpringResyncDetectionFilter.java
@caalador
caalador requested a review from heruan October 6, 2026 08:59
@heruan

heruan commented Oct 6, 2026

Copy link
Copy Markdown
Member

This removes public classes (ResyncDetectionFilter, SpringResyncDetectionFilter, ResyncDetector, ResyncInspector) that shipped in 5.0.0, so it is a breaking change in a patch release. Should we keep them on 5.0, maybe deprecated, and only add the event based counting? Also, if this lands, #434 is no longer needed.

@Artur-

Artur- commented Oct 6, 2026

Copy link
Copy Markdown
Member

Can those classes be used by users for something?

@caalador

caalador commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

434 is not needed after this one.

They were needed for manual setup in plain spring and standalone settings, but after this they have no use.

@caalador
caalador merged commit 0df2ff5 into 5.0 Oct 7, 2026
3 checks passed
@caalador
caalador deleted the cherry/cherry-pick-415-to-5.0 branch October 7, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants