How to handle backend (validation) errors while using Binder?

After writeBean() or writeBeanIfValid() normally the bean has to be saved in the backend.
While saving the bean in the backend any kind of error could occur (technical error, backend validation error, …).

The problem:
How can I handle the backend error and give the user the possibility to correct the input and try to save again?
The input fields and the bean contain the new values and Binder.hasChanges == false at that moment.
From the Binders point of view the complete write/save is done.
But from the applications/users point of view the state should be like before hitting the save button (bean values != field values, Binder.hasChanges == true).

I think I would need some kind of interceptor in doWriteIfValid() after validateBean() and before setHasChanges(false).
The interceptor calls the backend and tries to save the bean.
If there is any error in teh backend, the Interceptor could throw an Exception or return an error code.
An error from the interceptor should be handled in the same way as an validateBean() error.
The bean gets its old state and hasChanges stays true.

Would this be possible?
Any other idea how to handle backend errors/validation?

Hi,

I understand that you have validation rules in your backend Beans defined according to JSR-303 specs. If you want to use those as validation rules with Binder you need to use BeanValidator


https://vaadin.com/download/release/8.1/8.1.6/docs/api/com/vaadin/data/validator/BeanValidator.html

There are some missing features, but generally if you use BeanValidator and your input passes, there should not be backend errors since the validation is the same.

I do not use JSR-303.

The problem is, that copy data from UI to the bean ist not sufficient for real live applications.
The data in the bean will be used in some backend action (saved in the database as a simple case).
Every backend action can raise errors and this errors have to be handled.
Furthermore validation in the UI is not enough to ensure that all the data is correct. Binder will only validate single fields, not the whole data set.
I need a way to handle backend errors the same way, Binder handles validation errors. I would like to show an error message and give the user the change to try it again. Therefor the input/bean/Binder state has to be the state before the writeBean… action.

Example:

  • Form with input fields bound with Binder
  • call of Binder.readBean(bean)
  • User inputs new data in field → UI shows new data, Binder.hasChanges==true
  • User hits Save button → call of Binder.writeBeanIfValid()
  • Binder validates input → ok
  • Binder calls setter → input data copied to bean
  • Binder sets hasChanges = false
  • try to save the updated bean in the backend → error from backend

  • Problem
    : how to handle the backend error transparantly for the user?

To handle the backend error I would like to give the user the possibility to correct the input and try to save again.
But therefor I need to set the UI back to the state before the user hits Save.
The bean has to be reset and Binder.hasChanges has to set to true, This is the same action the Binder performs in case of validation errors (in doWriteIfValid()). If we would have a callback in doWriteIfValid() to perform the backend action, the problem would be solved.
Something like the following (in doWriteIfValid()):

[font=Courier New]
bindings.forEach(binding → binding.writeFieldValue(bean));
// Now run bean level validation against the updated bean
List binderResults = validateBean(bean);
boolean hasErrors = binderResults.stream()
.filter(ValidationResult::isError).findAny().isPresent();


if(!hasErrors) hasErrors = backendCallback.apply(bean);

if (hasErrors) {
// Bean validator failed, revert values
bindings.forEach((BindingImpl binding) → binding.setter
.accept(bean, oldValues.get(binding)));
} else {
// Write successful, reset hasChanges to false
setHasChanges(false);
}
[/font]

I am open for any other idea, but I think this would be a simple solution for real application problems.

Hi,

Why do you want to reset the bean and set the hasChanged to false ?
You’ve got an error in your backend then you can show it. (Label or Notification …)
Then your user can change the fields.

As said above the problem ist the state of the UI and the bean.
The UI looks like the data has been saved but that’s not true.

Binder.hasChanged() is false and there is no way to set it to true.
The bean has the same data as the UI but should have the original data (before Binder has called all the setters).

Image the user has filled 20 fields. He does not want to fill all the fields again only because there is a problem with one field.

Perhaps you can use a Validation on the binder (not on a field).
Here a preview/container of a Vaadin fiddle (don’t know if it will work, first time i tried):
Preview here:
https://vaadinfiddle.com/editor/preview/feb3f1137efca18816234a5e9715d4d0510677d01c7c07610df6dc7e268b02e2/src/main/java/org/vaadin/vaadinfiddle/MyUI.java
If you want to modify or fork it:
https://vaadinfiddle.com/editor/container/feb3f1137efca18816234a5e9715d4d0510677d01c7c07610df6dc7e268b02e2/src/main/java/org/vaadin/vaadinfiddle/MyUI.java

I copy/paste UI here (I use Vaadin 8.2.0.beta1 because in 8.1.6 “backend error” message is not displayed.

Person person = new Person("John", 26);


@Override
protected void init(VaadinRequest vaadinRequest) {
TextField firstName = new TextField("First name");

Binder<Person> binder = new Binder<>(Person.class);
binder.forField(firstName).withValidator(new BeanValidator(Person.class, "name")).bind("name");
binder.readBean(person);
binder.withValidator(bean -> backendValidate(bean),"backend error");

Button button = new Button("WRITE me");
Label labelError = new Label();
Label label = new Label();
labelError.setStyleName(ValoTheme.LABEL_FAILURE);
button.addClickListener((e) -> {
try {
binder.writeBean(person);
} catch (ValidationException e1) {
e1.printStackTrace();
}
label.setValue(binder.hasChanges()?"change":"no change");
});
binder.setStatusLabel(labelError);
setContent(new FormLayout(firstName,label, labelError, button));

}

private boolean backendValidate(Person bean) {
return "Robert".equals(bean.getName());
}

@Data
public class Person {

@Size(min = 4, max = 50)
private String name;

@Min(0)
@Max(100)
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

}

If there is an “backend error” then hasChanges return false and labelError display the error (in 8.2 and don’t display the error in 8.1.6).

But the bean is set (required to validate the bean).

Many thanks for the idea. I was not aware of the bean validation possibillities.
The withValidator JavaDoc even says, that the bean would be reverted to the previous state:

/**

  • Adds an bean level validator.
  • Bean level validators are applied on the bean instance after the bean is
  • updated. If the validators fail, the bean instance is reverted to its
  • previous state.
  • @see #writeBean(Object)
  • @see #writeBeanIfValid(Object)
  • @see #withValidator(SerializablePredicate, String)
  • @see #withValidator(SerializablePredicate, ErrorMessageProvider)
  • @param validator
  •        the validator to add, not null
    
  • @return this binder, for chaining
    */
    public Binder withValidator(Validator<? super BEAN> validator)

I will give it a try.

Using Binder.withValidator() as backend saver/validator works in principle.
Some downsides are in my opinion:

  • I think withValidator() was not build for this task
  • There can only be 1 error message from the Validator
  • A combination of true bean Validators and an pseudo validator to save the data in the database could be troublesome. The save pseudo Validator has to be the last to be called

1/ In my opinion, Binder.withValidator was done to validate a bean so I think it’s done for your use case. (but there may have some bugs)
2/ The validation result returns only one string, you can format it (or have to), if you want to display multiple messages. Perhaps you can use return one message in one HTML String and display it as HTML.
binder.setStatusLabel(formStatusLabel);
formStatusLabel.setContentMode(ContentMode.HTML);
3/ I think (don’t try) that withValidator are executed in the order they are linked for example.
binder.withValidator(validator1).withValidator(validator2).
→ validator1 is executed and if validator1 is ok then check validator2.
I don’t understand what the difference between “true bean validator” and “pseudo validator”. It’s only a object that returns ValidationResult ?

There are some bugs on binder in Vaadin 8.1 resolved in Vaadin 8.2beta, you may try the beta version.

PS: I saw you tried isValid, I think you should use writeBeanIfValid (made for buffered binder) instead isValid (made to unbuffered binder).
isValid javadoc:
Runs all currently configured field level validators, as well as all bean level validators if a bean is currently set with setBean(Object), and returns whether any of the validators failed.
writeBeanIfValid javadoc:
Writes changes from the bound fields to the given bean if all validators (binding and bean level) pass.
If any field binding validator fails, no values are written and false is returned.
If all field level validators pass, the given bean is updated and bean level validators are run on the updated bean. If any bean level validator fails, the bean updates are reverted and false is returned.