Discrepancies between Development and Production Builds

I have a medium-large sized Vaadin/Spring application that has been in development for a few months.

We noticed that on one view, the production build has an older version of the view that is missing two components.

If we run rebuild a run locally within the IDE, all is fine.

When I run ‘mvn clean package’ and run locally the UI is correct.

When I run ‘mvn clean package -Pproduction’ and run locally, the UI is incorrect.

We have disabled caching / run in incognito mode and the same issue happens.

If we debug the Production build locally - the components are generated but the UI does not display them.

I saw another thread on a similar matter and added the following to the Maven configuration:

<configuration>
     <optimizeBundle>false</optimizeBundle>
</configuration>

This has fixed the problem - but I feel this should not be a permanent solution for a production build.

Are there any solutions to diagnose and fix the underlying cause of this?

Many thanks,
P

Can you show us how you have build the problematic view and which components are not shown? Normally it’s how you have build the views.

Sure - previous version (which displays in production)


public class InputDialog extends Dialog
{

    public InputDialog( NewDataSourceObjectListener newDataSourceObjectListener )
    {
        VerticalLayout dialogLayout = new VerticalLayout();

        TextField urlField = new TextField( "URL" );
        urlField.setValue( "https://" );
        dialogLayout.add( urlField );

        dialogLayout.setPadding( false );
        dialogLayout.setSpacing( false );
        dialogLayout.setAlignItems( FlexComponent.Alignment.STRETCH );
        dialogLayout.getStyle().set( "width", "18rem" ).set( "max-width", "100%" );

        add( dialogLayout );

        Button saveButton = new Button( "Add", onSave());

        saveButton.addThemeVariants( LUMO_PRIMARY );
        Button cancelButton = new Button( "Cancel", e -> close() );
        getFooter().add( cancelButton );
        getFooter().add( saveButton );
    }
}

New version (that doesn’t display in production)

public class InputDialog extends Dialog
{

    public InputDialog( NewDataSourceObjectListener newDataSourceObjectListener )
    {
        VerticalLayout dialogLayout = new VerticalLayout();

        TextField urlField = new TextField( "URL" );
        urlField.setValue( "https://" );
        dialogLayout.add( urlField );

        IntegerField maxPages = new IntegerField( "Max pages" );
        maxPages.setStepButtonsVisible( true );
        maxPages.setValue( 100 );
        dialogLayout.add( maxPages );

        IntegerField maxDepth = new IntegerField( "Max depth" );
        maxDepth.setStepButtonsVisible( true );
        maxDepth.setValue( 3 );
        dialogLayout.add( maxDepth );

        dialogLayout.setPadding( false );
        dialogLayout.setSpacing( false );
        dialogLayout.setAlignItems( FlexComponent.Alignment.STRETCH );
        dialogLayout.getStyle().set( "width", "18rem" ).set( "max-width", "100%" );

        add( dialogLayout );

        Button saveButton = new Button( "Add", onSave());

        saveButton.addThemeVariants( LUMO_PRIMARY );
        Button cancelButton = new Button( "Cancel", e -> close() );
        getFooter().add( cancelButton );
        getFooter().add( saveButton );
    }
}

maxPages and maxDepth do not display in production - even though they are added to dialogLayout

If you are using IntegerField only there; this explains why it’s missing…

@legioth do you remember an issue with the byte code scanner and dialogs?

If I’m not mistaken I’ve seen this in the past and the culprit was that dialog.open() was called and because of the missing UI<->Dialog relation… it and it’s children weren’t included… if I remember correctly the intermediate fix was to also use add(dialog) on the view that’s displaying it

Great spot - add(dialog) fixed the issue - thank you!

What an interesting combination of factors leading to that issue.

I would think an issue should be raised for the byte code scanner to detect dialog.open() calls and treat them the same as add()?

I would think so too, let’s wait for Leif’s input.

I couldn’t reproduce the issue with the simplest possible example application. The integer field is rendered for me also in production mode. I tested with Vaadin 24.3.12 which is the latest stable version.

The dialog class:

public class MyDialog extends Dialog {
    public MyDialog() {
        add(new IntegerField());
    }
}

The main view:

@Route
@JsModule("./script.js")
public class MainView extends VerticalLayout {
    public MainView() {
        add(new Button("Show dialog", click -> new MyDialog().open()));
    }
}

And finally a dummy frontend/script.js file to trigger building an optimized production bundle rather than using the default precompiled production bundle.

console.log("loaded");

Do you still have the example on hand? In the past I’ve seen problems when e.g. the culprit / view is not the root. e.g. you have two views “” with a simple button and “with-dialog” - the second one has your code and now it won’t render when you navigate to it with a Routerlink; might be related to that.

Separate view makes no difference in my example. Still not able to reproduce.

As a side note, a RouterLink between the views forces them to be in the same chunk. With an Anchor, I can observe that a separate chunk is loaded when navigating. This is usually not a problem since the links are in a shared main layout rather than directly in any view. But not reproducing with a RouterLink either.

My two updated classes look like this:

@Route
@JsModule("./script.js")
public class MainView extends VerticalLayout {
    public MainView() {
        add(new Anchor("dialog", "Dialog view"));
    }
}
@Route
public class DialogView extends VerticalLayout {
    public DialogView() {
        add(new Button("Show dialog", click -> new MyDialog().open()));
    }
}

Thanks for checking! Good that all those gotchas that I remember are gone by now. Which makes it even more interesting what exactly is wrong in OP’s case.

I’m out of guesses so we would really need a reduced reproduction case from them to be able to investigate further.

Differences between dev and prod are one of the types of issues that we try to address with high priority since they are typically much more difficult to deal with for application developers compared to issues that you can reason about only based on running in development mode.

1 Like

I remember having a similar issue because I created a Dialog in a view with the Spring ObjectFactory and as a consequence the Dialog was not detected as part of the route. (then the client side of the component were not loaded)

My solution was to use only one frontend (Add @LoadDependenciesOnStartup on the class that implements AppShellConfigurator)

Unfortunately I think I didn’t create a ticket about this.

In short, this issue might come from how you are creating your dialog in your view.

ObjectFactory or any other association that cannot be picked up by the bytecode scanner is a separate case. I don’t see how this case would be related to dynamic loading if changing from dialog.open() to add(dialog) has an impact on the behavior.

Thank you for your feedback and attention with this issue.

I spent some time putting together a Vaadin Start application to replicate this issue - keeping as close as I can to the production application, but I cannot get the behaviour to replicate.

I have tried:

  • Adding the Dialog to the home screen
  • Adding the Dialog to a secondary screen linked via the nav menu
  • Adding the Dialog to a secondary screen via ui.navgate
  • Adding all of the Maven dependencies from the project

I can replicate the error on the production application if I remove the add( dialog ) call and run a production build.

I welcome any other suggestions for me to try to replicate this issue on a smaller scale.

Can you share some of your project information? Example:

  • everything in a single module?
  • all views below your application.java (if you run spring boot)?
  • can you show us your problematic route code? We’ve only seen the dialog so far and not the whole route

Hi Christian,

  • everything in a single module?

Yes - a single module.

  • all views below your application.java (if you run spring boot)?

I assume you mean from a package perspective - if so, yes - all views are in packages, which are level(s) below the Application class, and we are running Spring Boot.

  • can you show us your problematic route code? We’ve only seen the dialog so far and not the whole route

It is a Spring Boot application using Spring Security and JPA.

The application uses an AppLayout.

Here is the home screen:

@PageTitle("Home")
@Route(value = "home", layout = MainLayout.class)
@RouteAlias(value = "", layout = MainLayout.class)
@PermitAll
public class HomeView extends VerticalLayout
{

AppLayout contains a SideNav, via which you navigate to the DataView

nav.addItem( new SideNavItem( "Data", DataView.class ) );

DataView:

@PageTitle("Data")
@Route(value = "data", layout = MainLayout.class)
@RolesAllowed("Admin")
public class DataSourcesView extends VerticalLayout implements HasUrlParameter<String>
{
   //...
   Button addButton = new Button( "Add");
   addButton.addClickListener( event -> onAddData() );

   //...

   private void onAddData() {
      InputDialog dialog = new InputDialog();
      dialog.open();
   }
}

NewURLDialog

public class InputDialog extends Dialog
{
    public InputDialog()
    {
        VerticalLayout dialogLayout = new VerticalLayout();

        TextField urlField = new TextField( "URL" );
        urlField.setValue( "https://" );
        dialogLayout.add( urlField );

        IntegerField maxPages = new IntegerField( "Max pages" );
        maxPages.setStepButtonsVisible( true );
        maxPages.setValue( 100 );
        dialogLayout.add( maxPages );

        IntegerField maxDepth = new IntegerField( "Max depth" );
        maxDepth.setStepButtonsVisible( true );
        maxDepth.setValue( 3 );
        dialogLayout.add( maxDepth );

        dialogLayout.setPadding( false );
        dialogLayout.setSpacing( false );
        dialogLayout.setAlignItems( FlexComponent.Alignment.STRETCH );
        dialogLayout.getStyle().set( "width", "18rem" ).set( "max-width", "100%" );

        add( dialogLayout );

        Button saveButton = new Button( "Add", onSave());

        saveButton.addThemeVariants( LUMO_PRIMARY );
        Button cancelButton = new Button( "Cancel", e -> close() );
        getFooter().add( cancelButton );
        getFooter().add( saveButton );
    }
}

More info:

Java Version: 17
Vaadin Version: 24.3.8
Spring Boot Version: 3.2.3