0

I'm working on a program that uses a resource bundle for strings for easy translation. The structure is:

src/
└── main
    ├── java
    |   └── Main.java
    └── resources
        ├── Main.fxml
        ├── strings.properties
        ├── strings_en.properties
        └── strings_nl.properties

When I load the properties file in my start method (This is a JavaFX application) it works fine:

@Override
public void start(Stage primaryStage) throws java.io.IOException {
    resources = ResourceBundle.getBundle("strings", new Locale("nl"));
    Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("main.fxml"), resources);
    primaryStage.setTitle(resources.getString("title"));
    primaryStage.setScene(new Scene(root, 1280, 1024));
    primaryStage.show();
}

I can see this by the fact that the title of the window has the correct string, as set in the properties file. Then when I try to access the same resources variable elsewhere, I get a lang.NullPointerException:

@FXML
private void addItem(ActionEvent e) {
    try {
        ...
    } catch (NoResultException ex) {
        Alert alert = new Alert(AlertType.ERROR);
        alert.setTitle(resources.getString("err.unknownItem.title")); // Throws NullPointerException
        alert.setHeaderText(resources.getString("err.unknownItem.header"));
        alert.setContentText(resources.getString("err.unknownItem.text"));

        alert.show();
    }
}

I tried moving the code inside the catch-block to the start method, where it works fine.

The full stacktrace is

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8411)
    at com.sun.javafx.scene.control.behavior.TextFieldBehavior.fire(TextFieldBehavior.java:179)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:178)
    at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
    at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
    at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
    at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
    at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:216)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:148)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:247)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:246)
    at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
    at com.sun.glass.ui.View.notifyKey(View.java:966)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1769)
    ... 53 more
Caused by: java.lang.NullPointerException
    at Main.addItem(Main.java:116)
    ... 63 more
Cas Eliëns
  • 646
  • 12
  • 34

2 Answers2

2

I'm guessing from the comments below the question that you are using the same class for the Application class and for the controller class. Don't do this.. it causes mayhem.

Basically, the start() method is invoked on the instance of the Application subclass created by launch(). The event handler method is invoked on the instance of the controller class that is created by the FXMLLoader. If you use the same class for both (don't), then the resources field will be initialized in the instance created by launch(), but it won't be initialized in the instance created by the FXMLLoader (because it's a different object entirely).

Use a different class for the controller and the application.

Note that in a controller, you can define an initialize method which is automatically passed a reference to the resource bundle, so you can do:

public class Controller {

    private ResourceBundle resources ;

    public void initialize(URL location, ResourceBundle resources) {
        this.resources = resources ;
    }

    // ..
}

Also note that the following, while undocumented, also seems to work:

public class Controller {

    @FXML
    private ResourceBundle resources ;

    // ...

}

Here you must use the field name resources.

Community
  • 1
  • 1
James_D
  • 177,111
  • 13
  • 247
  • 290
  • I never knew that was how launching an application worked, thanks! I just moved the controller to a separate class and passed the command line arguments to it, works like a charm now! Thanks! – Cas Eliëns Aug 22 '16 at 17:27
  • @cascer1 No need to pass the command line arguments; see update for a couple of cleaner ways of making it work. – James_D Aug 22 '16 at 17:29
  • I'm sorry, I never mentioned this. I have some command line arguments used for setting up the database connection in the controller. I passed *those* to the controller. I used [this answer](http://stackoverflow.com/a/14190310/4385713) to separate the controller from the launcher and pass the arguments. – Cas Eliëns Aug 22 '16 at 17:33
  • But you presumably want to pass the resource bundle to the FXMLLoader (so that you can do [resource resolution](http://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#resource_resolution) inside the FXML file). So you would need the resource bundle before then. There's no point in looking it up in the database twice; just pass the resource bundle to the loader when you load the FXML and let the `FXMLLoader` pass it to the controller as above. Sounds like you are making the code more convoluted than it needs to be. But anyway.. I suppose if it works... – James_D Aug 22 '16 at 17:43
  • The resources and database are completely separate. The resources are indeed passed to the FXMLLoader by the launcher. There are two database connections in the resources, a local development connection and a production connection. I can switch between them using a command line argument. The switching happens in the controller, where the connection is established. – Cas Eliëns Aug 22 '16 at 17:46
  • Ah, ok, got it. That makes sense. – James_D Aug 22 '16 at 17:49
0

add

resources = ResourceBundle.getBundle("strings", new Locale("nl"));

to your addItem(ActionEvent e) method

spirit
  • 2,922
  • 2
  • 9
  • 27
  • Sort of defeats the purpose, doesn't it? The resource bundle should be specified at startup time. – James_D Aug 22 '16 at 17:00
  • yeah, but maybe his is loosed it somewhere =). maybe `resource` is `null`... need to check that. – spirit Aug 22 '16 at 17:02
  • Check it with `if (resources==null) { throw new IllegalStateException("resources is null"); }`. But it's pretty clear that's the case anyway. – James_D Aug 22 '16 at 17:03