-
Notifications
You must be signed in to change notification settings - Fork 0
How To Use the Latest Vaadin 8 Binder Features
Vaadin 8.30 includes several Binder features that never made it into the free 8.14.3 line or the early tutorials. They were added for real, large business applications that needed better control over long‑lived forms, dirty tracking and drafts.
This guide shows how to use those newer APIs with minimal examples, and how they solve concrete problems that you typically run into only once your app (and team) grows.
We focus on these additions:
- Change detection and dirty tracking:
setChangeDetectionEnabled,hasChanges,getChangedBindings,getFields - Safer writes:
writeBeanIfValid - Draft support:
writeBeanAsDraft
All examples use Vaadin 8 Binder, but they work only in the newer 8.XX extended maintenance builds (such as 8.30) – not in the original 8.0–8.14 examples you see in public docs.
The standard Binder docs mostly show this pattern:
Binder<Product> binder = new BeanValidationBinder<>(Product.class);
binder.bindInstanceFields(this); // basic field binding
binder.readBean(product); // load data
// later: binder.writeBean(product); // save dataIn real business apps you usually also need to know if anything has changed and which fields are dirty. That is what Binder’s change detection support is for.
Binder<Product> binder = new BeanValidationBinder<>(Product.class);
binder.bindInstanceFields(this);
// Newer Vaadin 8 feature
binder.setChangeDetectionEnabled(true);With change detection enabled, Binder tracks which bindings have been modified by the user.
A classic requirement is: enable Save/Discard only when there are changes and the form is valid.
binder.setChangeDetectionEnabled(true);
binder.addStatusChangeListener(event -> {
boolean isValid = !event.hasValidationErrors();
boolean hasChanges = binder.hasChanges(); // newer API
saveButton.setEnabled(hasChanges && isValid);
discardButton.setEnabled(hasChanges);
});Problems this solves in big apps
- Avoids accidental no‑op saves on every click.
- Prevents saving invalid forms when some fields are still failing validation.
- Centralizes the enable/disable logic instead of scattering it around listeners.
In complex UIs you often want to show where the user has changed something: highlight fields, show “was …” tooltips, etc. This becomes critical in workflows where forms stay open for a long time.
Binder exposes the changed bindings when change detection is enabled:
binder.setChangeDetectionEnabled(true);
private void updateDirtyIndicators(Product currentProduct) {
// Remove old indicators first
clearDirtyIndicators();
binder.getChangedBindings().forEach(binding -> {
AbstractComponent field = (AbstractComponent) binding.getField();
field.addStyleName("dirty-field");
Object oldValue = binding.getGetter().apply(currentProduct);
if (oldValue != null) {
field.setDescription("Was: " + oldValue); // minimal example
}
});
}You can pair this with a simple helper that clears indicators for all bound fields:
private void clearDirtyIndicators() {
binder.getFields().forEach(hasValue -> {
AbstractComponent field = (AbstractComponent) hasValue;
field.removeStyleName("dirty-field");
field.setDescription(null);
});
}Here, both getChangedBindings() and getFields() are part of the newer Binder APIs that don’t appear in the older public examples.
Typical big‑app use cases
- Long forms where the user edits only a few fields and needs to quickly see what changed.
- Support teams inspecting a customer session and needing visual hints for modified values.
- Review workflows where someone else needs to verify changes before approving.
Older tutorials usually call writeBean(bean) directly and let it throw on validation errors. In large applications that leads to noisy logs and awkward error handling.
Newer Binder versions include writeBeanIfValid:
private void handleSave() {
if (currentProduct != null && binder.writeBeanIfValid(currentProduct)) {
fireEvent(new SaveEvent(this, currentProduct));
return;
}
// Here we know validation failed; decide how to react
showAvailabilityErrorIfNeeded();
}This method:
- Runs all field‑ and bean‑level validators.
- Returns
trueonly if everything is valid and values were written to the bean. - Never throws a validation exception – you keep control of error reporting.
Why this matters in business apps
- Makes save logic more predictable and testable.
- Plays nicely with cross‑field invariants (for example,
availabilityversusstockCount). - Avoids exception spam from users trying to save incomplete forms.
In big internal systems, users often:
- Keep a form open for a long time.
- Navigate away, close the browser, or lose their VPN connection.
- Expect their work not to be lost.
That’s what writeBeanAsDraft is designed for.
A common pattern is to capture unsaved changes when the UI or form is detached:
@Override
public void detach() {
super.detach();
if (isShown() && binder.hasChanges()) { // newer API
Product original = getProduct();
if (original != null) {
Product draft = new Product(original); // copy constructor
// Newer Binder feature
binder.writeBeanAsDraft(draft, true);
saveDraft(draft); // your persistence logic
}
}
}Key points:
-
binder.hasChanges()checks if the user has modified any bound field. -
writeBeanAsDraft(draft, true)writes only the valid values into the draft bean and marks the current changes as handled (so the form is considered clean afterward). - You can store the draft in a database, cache, or per‑user draft table.
Binder does not merge drafts automatically; you keep full control over how to apply them. A typical strategy is:
- Load the draft when the user returns.
- Copy the draft values into the active bean and fields.
- Re‑run dirty tracking to show which values came from the draft.
In pseudo‑code:
public void mergeDraft(Product draft) {
// Convert and copy only the properties you care about
productName.setValue(draft.getProductName());
stockCount.setValue(draft.getStockCount());
// ...other fields...
updateDirtyIndicators(draft); // reuse change‑highlighting logic
}Real‑world problems this solves
- Users losing 20 minutes of data entry because they clicked back or their session expired.
- Complex backoffice forms where drafts are reviewed and finished later.
- Multi‑step flows where a draft may be saved between steps without committing the whole transaction.
The newer Binder features work well together with bean‑level validation, which is very common in business apps (cross‑field rules, invariants, etc.).
Example of an availability vs. stock rule:
binder.withValidator(product ->
(product.getAvailability() == Availability.AVAILABLE
&& product.getStockCount() > 0)
|| (product.getAvailability() == Availability.DISCONTINUED
&& product.getStockCount() == 0),
"Availability does not match stock count");
binder.setChangeDetectionEnabled(true);
binder.addStatusChangeListener(event -> {
boolean isValid = !event.hasValidationErrors();
boolean hasChanges = binder.hasChanges();
saveButton.setEnabled(hasChanges && isValid);
});Here Binder:
- Evaluates a real business rule spanning multiple fields.
- Tracks whether anything changed.
- Surfaces both pieces of information in one place (the status listener).
The newer Binder APIs were driven by feedback from teams running large Vaadin 8 applications in production. You probably need them when:
- Your forms are long‑lived (open in a tab for hours) and you must avoid losing unsaved work.
- Users expect drafts and resumable editing.
- You need precise dirty tracking – not just “the form changed”, but which fields changed and what they were before.
- You want your save buttons to reflect the actual form state instead of home‑grown boolean flags.
If your code currently looks like the simple examples in the original Vaadin 8 docs, these newer methods let you grow that design into something that scales to real enterprise workflows, while still staying inside the familiar Binder API.
Newer Vaadin 8 Binder builds add exactly the pieces that big applications were missing:
-
setChangeDetectionEnabled,hasChanges,getChangedBindings,getFieldsto build robust dirty tracking and UX around it. -
writeBeanIfValidfor predictable, exception‑free saves. -
writeBeanAsDraftto implement autosave and draft workflows without reinventing your own binding layer.
You can start from the official documentation examples and then incrementally layer these features on top wherever you need more resilience and insight into what your users are doing with your forms.