bug in Binder (v14.1.28)

This is very closely related to what I posted earlier, but I don’t think this should happen either.

 @Transactional
 void saveClickListener() {
        binder.writeBeanIfValid(result); //line 134
java.lang.NullPointerException: null
	at com.vaadin.flow.data.binder.ValidationResultWrap.isError(ValidationResultWrap.java:86) ~[flow-data-2.1.9.jar:2.1.9]

	at com.vaadin.flow.data.binder.BindingValidationStatus.<init>(BindingValidationStatus.java:98) ~[flow-data-2.1.9.jar:2.1.9]

	at com.vaadin.flow.data.binder.Binder$BindingImpl.toValidationStatus(Binder.java:1081) ~[flow-data-2.1.9.jar:2.1.9]

	at com.vaadin.flow.data.binder.Binder$BindingImpl.doValidation(Binder.java:1092) ~[flow-data-2.1.9.jar:2.1.9]

	at com.vaadin.flow.data.binder.Binder$BindingImpl.validate(Binder.java:1035) ~[flow-data-2.1.9.jar:2.1.9]

	at com.vaadin.flow.data.binder.Binder.lambda$doWriteIfValid$3(Binder.java:1814) ~[flow-data-2.1.9.jar:2.1.9]

	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na]

	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654) ~[na:na]

	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na]

	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na]

	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na]

	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]

	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na]

	at com.vaadin.flow.data.binder.Binder.doWriteIfValid(Binder.java:1814) ~[flow-data-2.1.9.jar:2.1.9]

	at com.vaadin.flow.data.binder.Binder.writeBeanIfValid(Binder.java:1788) ~[flow-data-2.1.9.jar:2.1.9]

	at com.wodiq.ui.views.EnterResultsView.saveClickListener(EnterResultsView.java:134)

It’s caused by the presence of a binding for a field that wasn’t used in this instance.

EnterResultsView() {
        time = new TextField("Time");
		timeBinding = binder.forField(time).withValidator(this::timeValidator, "Invalid value").withConverter(new TimeConverter()).bind(Result::getTime, Result::setTime);

The resultType will not match in this instance so it should default to valid.

private boolean timeValidator(String time) {
        if (resultType == TIME || resultType == TOTAL_ELAPSED_TIME || resultType == TIME_TENTH_SEC) {
            if (!time.contains(":"))
                return false;
            String minutesSubstring = time.substring(0, time.indexOf(":"));
            String secondSubstring = time.substring(time.indexOf(":") + 1);
            if (resultType == TIME_TENTH_SEC) {
                try {
                    new SimpleDateFormat("mm:ss.S").parse(time);
                    double seconds = Double.parseDouble(secondSubstring);
                    if (seconds > 59.9)
                        return false;
                    Integer.parseInt(minutesSubstring);
                } catch (NumberFormatException | ParseException e) {
                    return false;
                }
            } else {
                try {
                    new SimpleDateFormat("mm:ss").parse(time);
                    int seconds = Integer.parseInt(secondSubstring);
                    if (seconds > 59)
                        return false;
                    Integer.parseInt(minutesSubstring);
                } catch (NumberFormatException | ParseException e) {
                    return false;
                }
            }
        }
        return true;
    }

I can get around the null pointer exception by doing this, but given the fact that I didn’t make the field required, this seems like a hack that shouldn’t be necessary.

 @Transactional
    void saveClickListener() {
        switch (resultType) {
            case ROUNDS_REPS:
                timeBinding.unbind();
                binder.writeBeanIfValid(result);
                timeBinding = binder.forField(time).withValidator(this::timeValidator, "Invalid value").withConverter(new TimeConverter()).bind(Result::getTime, Result::setTime);
                break;
            default:
                binder.writeBeanIfValid(result);

Hi Brian,

Does it fail because the value is empty or null? The reason i ask is because of

    if (!time.contains(":"))
                return false;

Which would result in an NPE or returning false if the value is an empty string, assuming resulttype is one of the values specified in your code.

I’m on 14.1.27, but generally have not had any binder issues with null values.

If its a bug in Vasdin you should also file an issue in github.

Hi Martin,

No, the resultType is none of those that match. Thus, that entire code block should get skipped and true should be returned.