How should ViewModels and Services communicate in a JavaFX + Spring Boot MVVM application?

I'm building a JavaFX + Spring Boot application using the MVVM pattern. I’m building a generic “wizard” in a JavaFX + Spring Boot MVVM app. A WizardViewModel drives a sequence of steps (STEP_ONE → STEP_TWO → STEP_THREE → …), and each step has its own StepViewModel (e.g. StepOneViewModel) that performs some background work via javafx.concurrent.Task. Current (tightly coupled) approach Each StepViewModel currently does this when it finishes: // in StepOneViewModel masterWizardViewModel.nextStep(); // directly advances the wizard That creates a direct dependency between child and master VMs. Two candidate decoupling strategies 1. JavaFX Property Binding Have each StepViewModel expose a simple BooleanProperty doneProperty(), and let WizardViewModel listen: // StepOneViewModel.java @Component public class StepOneViewModel { private final BooleanProperty done = new SimpleBooleanProperty(false); public BooleanProperty doneProperty() { return done; } public void completeStep() { // your background Task succeeded… done.set(true); } } // WizardViewModel.java @Component public class WizardViewModel { @Autowired StepOneViewModel stepOneVm; public void initialize() { stepOneVm.doneProperty().addListener((obs, was, done) -> { if (done) advanceTo(Step.TWO); }); } private void advanceTo(Step next) { /* … */ } } Pros No extra Spring boilerplate. Direct, type-safe JavaFX binding. Cons WizardViewModel must know and inject every StepViewModel. Harder to reuse outside JavaFX thread. 2. Spring ApplicationEvents Publish an event from the child VM and let WizardViewModel subscribe: // StepCompletedEvent.java public class StepCompletedEvent extends ApplicationEvent { private final Step step; public StepCompletedEvent(Object src, Step step) { super(src); this.step = step; } public Step getStep() { return step; } } // StepOneViewModel.java @Autowired ApplicationEventPublisher events; public void completeStep() { events.publishEvent(new StepCompletedEvent(this, Step.ONE)); } // WizardViewModel.java @Component public class WizardViewModel { @EventListener public void onStepCompleted(StepCompletedEvent e) { advanceTo(e.getStep().next()); } private void advanceTo(Step next) { /* … */ } } Pros Loose coupling—publisher doesn’t need to know subscribers. Works across UI, background services, other modules. Cons More boilerplate (event class + listener). Must ensure UI updates happen on the JavaFX thread. Questions Did I miss any significant trade-offs between these two patterns? In a mid-sized JavaFX + Spring Boot MVVM project, which approach have you found works best for decoupling ViewModels—and why? If you’ve implemented one or both, what pitfalls or gotchas did you encounter? Thanks for any advice or example code you can share!

May 22, 2025 - 18:40
 0

I'm building a JavaFX + Spring Boot application using the MVVM pattern.

I’m building a generic “wizard” in a JavaFX + Spring Boot MVVM app. A WizardViewModel drives a sequence of steps (STEP_ONE → STEP_TWO → STEP_THREE → …), and each step has its own StepViewModel (e.g. StepOneViewModel) that performs some background work via javafx.concurrent.Task.

Current (tightly coupled) approach Each StepViewModel currently does this when it finishes:

// in StepOneViewModel
masterWizardViewModel.nextStep(); // directly advances the wizard

That creates a direct dependency between child and master VMs.

Two candidate decoupling strategies

1. JavaFX Property Binding

Have each StepViewModel expose a simple BooleanProperty doneProperty(), and let WizardViewModel listen:

// StepOneViewModel.java
    @Component
    public class StepOneViewModel {
      private final BooleanProperty done = new SimpleBooleanProperty(false);
      public BooleanProperty doneProperty() { return done; }
    
      public void completeStep() {
        // your background Task succeeded…
        done.set(true);
      }
    }
    
    // WizardViewModel.java
    @Component
    public class WizardViewModel {
      @Autowired
      StepOneViewModel stepOneVm;
    
      public void initialize() {
        stepOneVm.doneProperty().addListener((obs, was, done) -> {
          if (done) advanceTo(Step.TWO);
        });
      }
    
      private void advanceTo(Step next) { /* … */ }
    }

Pros

  • No extra Spring boilerplate.

  • Direct, type-safe JavaFX binding.

Cons

  • WizardViewModel must know and inject every StepViewModel.

  • Harder to reuse outside JavaFX thread.

2. Spring ApplicationEvents

Publish an event from the child VM and let WizardViewModel subscribe:

// StepCompletedEvent.java
public class StepCompletedEvent extends ApplicationEvent {
  private final Step step;
  public StepCompletedEvent(Object src, Step step) { super(src); this.step = step; }
  public Step getStep() { return step; }
}

// StepOneViewModel.java
@Autowired ApplicationEventPublisher events;
public void completeStep() {
  events.publishEvent(new StepCompletedEvent(this, Step.ONE));
}

// WizardViewModel.java
@Component
public class WizardViewModel {
  @EventListener
  public void onStepCompleted(StepCompletedEvent e) {
    advanceTo(e.getStep().next());
  }

  private void advanceTo(Step next) { /* … */ }
}

Pros

  • Loose coupling—publisher doesn’t need to know subscribers.

  • Works across UI, background services, other modules.

Cons

  • More boilerplate (event class + listener).

  • Must ensure UI updates happen on the JavaFX thread.

Questions

  1. Did I miss any significant trade-offs between these two patterns?
  2. In a mid-sized JavaFX + Spring Boot MVVM project, which approach have you found works best for decoupling ViewModels—and why?
  3. If you’ve implemented one or both, what pitfalls or gotchas did you encounter?

Thanks for any advice or example code you can share!