javavaadinquarkusbean-validationhibernate-validator

Vaadin Bean Validation not working after Quarkus Live Reload


I'm playing around with Quarkus 3.6.3 and Vaadin 24.3.0. I have the problem that the required indicator for mandatory fields are not shown any more when the quarkus application is live reloaded in dev mode due to changes in any of the class files.

Java classes

I have a view showing some text fields and I use BeanValidationBinder to bind the fields to an instance of a JPA entity class User. This class is simple and looks like this:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class User {

    @NotNull
    @Column
    private String firstName;

    @NotNull
    @Column
    private String lastName;

    // Getters and Setters
}

For the sake of simplicity, the view class looks like this:

@Route(value = "user", layout = MainLayout.class)
public class UserFormView extends VerticalLayout {

    private final TextField firstName = new TextField("First name");

    private final TextField lastName = new TextField("Last name");

    private final Binder<User> binder = new BeanValidationBinder<>(User.class);

    public UserFormView() {

        binder.forField(firstName).withNullRepresentation("").bind("firstName");
        binder.forField(lastName).withNullRepresentation("").bind("lastName");
        binder.readBean(new User());

        add(firstName, lastName);
    }
}

The problem

After a live reload the variable propertyDescriptor in the method BeanValidationBinder#configureRequired is null and thus the bean validation annotations of the domain class are not processed for setting the required indicator and for the binder's validation.

Now I wonder if the required indicators should also be shown correctly after live reload.

Debugging

From debugging I figured out:


Solution

  • Thanks for the detective work. That is a bug for sure.

    From what I can see, there's not much we can do in Quarkus. Vaadin Flow should avoid to store the ValidatorFactory in a static field in LazyFactoryInitializer#FACTORY. Or at least clean things up when the app is stopped and create a new one then. The current code would cause class loader leaks with any server reloading the app anyway.

    Following this comment by Ivan Kaliuzhnyi in the Flow tracker https://github.com/vaadin/flow/issues/4481#issuecomment-1712720926, you should be able to work around it even if it's not pretty at all.

    Pasting from the comment to keep history:

    @Configuration
    public class ValidatorConfig implements InitializingBean {
    
        @Autowired
        private ValidatorFactory validatorFactory;
    
        @Bean
        public ValidatorFactory validatorFactory(MessageSource messageSource) {
            var bean = new LocalValidatorFactoryBean();
            bean.setValidationMessageSource(messageSource);
            return bean;
        }
    
        @Bean
        public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(ValidatorFactory validatorFactory) {
            // AvailableSettings.JPA_VALIDATION_FACTORY -> javax.persistence.validation.factory
            // AvailableSettings.JAKARTA_VALIDATION_FACTORY -> jakarta.persistence.validation.factory
            return hibernateProperties ->
                hibernateProperties.put(AvailableSettings.JAKARTA_VALIDATION_FACTORY, validatorFactory);
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
    
            var className = "com.vaadin.flow.data.validator.BeanValidator.LazyFactoryInitializer";
            var fieldName = "FACTORY";
            var field = FieldUtils.getDeclaredField(ClassUtils.getClass(className), fieldName, true);
    
            var unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            var unsafe = (Unsafe) unsafeField.get(null);
    
            unsafe.putObject(unsafe.staticFieldBase(field),
                    unsafe.staticFieldOffset(field),
                    validatorFactory);
    
        }
    }