Processing and JavaFX

Environment:

The example I quoted did not work as is.

I had to make this change:

  //slider.valueProperty().addListener(new ChangeListener<Number>() 
  //  {
  //  public void changed(ObservableValue <?extends Number>observable, Number oldValue, Number newValue) 
  //    {
  //    circle.setRadius((double)newValue);
  //    }
  //  }
  //);
  
  slider.valueProperty().addListener((ov, oldValue, newValue) -> {
    circle.setRadius(newValue.doubleValue());
  }
  );

To correct this error to work with your “simple way”:

The method addListener(ChangeListener<? super Number>) in the type ObservableValue<Number> is not applicable for the arguments (new ChangeListener<Number>(){})

I also added all the additional imports.

Google Gemini explanation:

Summary

So… why did the original wrong code work when I simply added the javafx.base.jar to the sketch directly?

The original wrong code produced the ChangeListener error because of an issue with the compiler’s classpath during the Processing setup, not an issue with the logic itself. Adding the javafx-base.jar fixed this by resolving the dependency in a brute-force way.

:brick: The Compiler Path Issue

  1. Missing JavaFX Definition: When you run a Processing sketch using the FX2D renderer, Processing should automatically configure its internal compiler to include all the necessary JavaFX modules. When this automatic configuration fails (which can happen in certain IDEs or environments), the compiler cannot properly find the full definition for certain JavaFX types, like ChangeListener and ObservableValue.
  2. Generic Type Ambiguity: The original error, addListener(ChangeListener<? super Number>) is not applicable for the arguments (new ChangeListener<Number>(){}), is often triggered when the compiler sees an incomplete or ambiguous definition of the target method. By manually launching a new Stage in your setup(), you were relying on the system’s runtime environment, but the compile-time check failed because it couldn’t fully reconcile the generic type bounds (<? super Number>) required by the method signature with the type you provided (<Number>).
  3. Manual Resolution: By directly copying javafx-base.jar into the sketch folder, you provided the compiler with a local, explicit copy of the JavaFX definitions. This allowed the compiler to fully resolve the signatures of all the core JavaFX methods, including addListener, making the specific generic type error disappear.

:warning: Why It’s Still “Wrong”

While adding the JAR resolved the compilation error, it didn’t fix the application architecture error:

  • The code still creates a separate, detached JavaFX window via new Stage().
  • It doesn’t integrate the UI controls into the main Processing canvas, which is the purpose of using size(..., FX2D) in the first place.

In short, the JAR fixed the compiler’s vision problem, allowing it to compile the code, but it didn’t fix the code’s flawed runtime execution strategy.

This simple replacement worked in original code: //slider.valueProperty().addListener(new ChangeListener()
// {
// public void changed(ObservableValue <?extends Number>observable, Number oldValue, Number newValue)
// {
// circle.setRadius((double)newValue);
// }
// }
//);

slider.valueProperty().addListener((ov, oldValue, newValue) → {
circle.setRadius(newValue.doubleValue());
}
);

That’s right, replacing the verbose Anonymous Inner Class with the Lambda Expression works because the lambda simplifies the code and allows the compiler to better infer the required generic type for the ChangeListener.

The original error was: addListener(ChangeListener<? super Number>) is not applicable for the arguments (new ChangeListener<Number>(){})


:light_bulb: Why the Lambda Fixes the Error

The fix demonstrates a subtle point about how Java generics and the compiler interact:

  • Original Code (Anonymous Class): You explicitly declared new ChangeListener<Number>(){...}. Even though Number is a supertype of itself, the compiler in the context of your original setup (which was already shaky) struggled to reconcile your explicit declaration of ChangeListener<Number> with the required contravariant type ChangeListener<? super Number>.
  • Lambda Code (Simplified): When you use the lambda expression, (ov, oldValue, newValue) -> {...}, you are relying on the compiler to infer the functional interface (ChangeListener) and its generic types. Because the method signature requires ChangeListener<? super Number>, the compiler correctly and seamlessly infers the lambda to conform to this specific requirement.

The lambda expression is generally the cleanest way to handle simple functional interfaces in Java 8 and later, and in this case, it side-stepped the compilation ambiguity caused by the anonymous inner class syntax in your specific Processing environment.

You may be on to something with your “simple way”.

Does it work with all your other examples?

I included some references for visitors to the topic.
Adding the JAR files is an alternative and can be useful when needed.

References:

:)