JDK-8150951 : ComboBoxPopupControl: custom subclasses throw NPE
  • Type: Bug
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: 9
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2016-03-01
  • Updated: 2025-09-24
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
There are two variants, one happens on hiding the popup the other on moving the mouse over the arrow. Below is a minimal example to reproduce both:

Variant A:
- run the example as-is
- open popup
- hide popup and see the NPE

top of stacktrace:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
	at javafx.scene.control.skin.ComboBoxPopupControl.lambda$createPopup$913(ComboBoxPopupControl.java:464)
	at javafx.stage.PopupWindow.doAutoHide(PopupWindow.java:840)

Variant B:
- uncomment the line setting editable to true, compile and run
- move mouse over arrow and see NPE

Top of stacktrace:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
	at javafx.scene.control.skin.ComboBoxBaseSkin.lambda$new$900(ComboBoxBaseSkin.java:108)
	at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)

The technical reason for both is the access to the behavior via getBehavior which is package-private (thus can't be overridden) and defaults to null.

A fix might be tricky: can't widen the method scope because Behavior is private api which must not leak into public api (I assume those are the rules?)

A working hack is twofold: 
- create our own popup (c&p core, replacing getBehavior by our own) and reflectively replace super's field value
- make sure we set editability only after the skin is installed 

The example: 

public class ComboCustomSkinNPE_Report extends Application {

    /**
     * Minimal custom skin and behavior.
     */
    private static class CustomComboSkin<T> extends ComboBoxPopupControl<T> {

        private Pane content;
        private Label display;
        private ComboBoxBaseBehavior<T> behavior;
 
        public CustomComboSkin(ComboBoxBase<T> control) {
            super(control);
            // most basic behavior
            behavior = new CustomComboBehavior<>(control);
            getPopupContent().setManaged(false);
            getChildren().add(getPopupContent());
            getChildren().add(getDisplayNode());
        }
        
        //---------- implement abstract methods
        @Override
        protected Node getPopupContent() {
            if (content == null) {
                content = new VBox(10);
                for (int i = 0; i <5; i++) {
                    content.getChildren().add(new Label("dummy-item " + i));
                }
                
            }
            return content;
        }

        @Override
        protected TextField getEditor() {
            return null;
        }

        @Override
        protected StringConverter<T> getConverter() {
            return null;
        }

        @Override
        public Node getDisplayNode() {
            if (display == null) {
                display = new Label("nothing real");
            }
            return display;
        }
        
    }
    
    private static class CustomComboBehavior<T> extends ComboBoxBaseBehavior<T> {

        public CustomComboBehavior(ComboBoxBase<T> comboBox) {
            super(comboBox);
        }
        
    }

    private Parent getContent() {
        ComboBox customFromStart = new ComboBox(FXCollections.observableArrayList("one", "two", "three")) {

            @Override
            protected Skin createDefaultSkin() {
                return new CustomComboSkin<>(this);
            }
            
        };
        // if editable _before_ installing skin, throws NPE on moving mouse over arrow
        // customFromStart.setEditable(true);
        Pane coreLane = new HBox(10, new Label("combo core with custom skin"), customFromStart);
        
        return new VBox(10, coreLane);
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent()));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Comments
Targeted to 10 as an issue introduced in 8u or 9
17-02-2017

Approved by component triage team to defer
23-06-2016

As noted in previous comments, this issue is due to the fact that we are straddling the use of public and non-public API, and running into issues when doing so. All approaches to resolving this issue fall down due to the fact that, ideally, we would also have the behaviors public, and anything we do alternatively is really working around these missing classes. This issue only becomes apparent, I believe, when a custom skin is created that uses the non-public behavior classes. I might be wrong here, but if that is the case then it is my preference to defer this issue out of JDK 9 to allow for the possibility of this being resolved the right way - by making behaviors public. In the mean time, the workaround for this issue is to duplicate the functionality present in the behavior in the custom skin code (e.g. show / hide / etc).
15-06-2016

Whichever way we look at it, the problem remains that there is no access to the behavior functionality (especially so starting JDK 9). This means that the custom skin developer is required to create their own code that handles showing / hiding the popup when the arrow button is clicked. The question I have is whether it is better to have some API that allows this, or whether we put this on hold until such time that behaviors also become public. I'm attaching yet another prototype approach that could be used to enable the required functionality (and to avoid the NPEs), but I'm not convinced whether this is better than doing nothing and waiting until behaviors are public too. Any thoughts would be appreciated.
12-04-2016

Mainly yes, I was looking into options that wouldn't expose the implementation. I wasn't particularly fond of any of them either, so it might be the case that some considered exposing is in order. I'll take another look into this sometime soon.
31-03-2016

possible - but not entirely happy: those skin.onXX are mirrowing the methods on the behavior thus introducing a more than only weak coupling. Such delegates tend to explode ... Would like to understand why you want to hide the arrow so much that you prefer to pay the price for the cover methods? Too much of an implementation detail?
24-03-2016

Attaching a second patch for discussion. The API naming is weak, and there is no documentation, but this approach gives a few methods you can override to handle the arrow button interactions (without exposing the arrow button itself).
22-03-2016

That was the intent of my patch - to understand whether you simply wanted to avoid the NPE, or whether you wanted the same access as the default skins. I will look into this further when time permits.
02-03-2016

hmm .. patches are a bit hard to read, anyway: they look like you go for a technical null check (beautified by an Optional, which I think is most unnecessary, but then ... personal taste :-). If so, that wouldn't really fix the issue beyond the mere technicality: custom subclasses must have a hook to implement the functionality (== correct opening/closing of the popup) - that's what the meat the this report is about and why I evaluated any fix is "tricky" :-) With your patch, they still can't. An option might be protected methods that install the listeners without exposing the behaviour, f.i. something like (from the top of my head, names and exact signature very open to debate) for the mouse handlers: /** * Must install listeners for mouse actions on the arrrowButton. * Default implementation delegates to behavior if available, does nothing * otherwise. */ protected void installArrowHandlers(Node arrowButton) { arrowButton.addEventHandler(..... e -> executeBehaviour(b -> b.mousePressed)); ... } Custom subclasses simply re-implement as they need to. For the popup creation/installation you might consider to make the createPopup a real factory method, that is protected and returning the popup: /**Creates, and returns the PopupControl. */ protected PopupControl createAndConfigurePopupControl() { // default same as you do return popup; } again subclasses can completely re-implement.
02-03-2016

Updated patch file. Initial patch included changes from another fix I was working on.
01-03-2016

One approach is to do what I've done in the attached patch. This approach basically means functionality won't work if the behavior is not set.
01-03-2016