Issue
I'm working on a project where I'd like to use an external CSS file with JavaFX to style the VBox and panes and such.
I've used the line below to try to add the style sheet to the respective scene:
scene.getStylesheets().add(getClass().getResource("/style.css").toExternalForm());
This gave me null pointers, I read some other posts on this issue and found that this is often due to the stylesheet not being in the class path that it is trying to be accessed from. Here is a screenshot that allows you to see that this is not the case:
You'll notice there is Styles.css and style.css, I tried both for different troubleshooting purposes
I also found suggestions from people saying that if it is in the class path, that it should be accessed as such:
scene.getStylesheets().add("style.css");
However doing so is giving me
Nov 04, 2018 9:24:10 PM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
WARNING: Resource "Styles.css" not found.
I'm open to any suggestions, I'm working in IntelliJ using maven.
EDIT: Here is the pom file for further investigation -
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.almasb</groupId>
<artifactId>CashMachine</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<source.version>1.8</source.version>
<!-- plugins -->
<maven.compiler.version>3.3</maven.compiler.version>
<maven.shade.version>2.4.2</maven.shade.version>
<!-- dependencies -->
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${source.version}</source>
<target>${source.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.almasb.atm.CashMachineApp</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Solution
Maven convention uses a standard directory layout and, unless otherwise configured, requires that you follow it. One of the things the standard directory layout does is separate Java source files (.java
) from resource files (everything not .java
). It looks like this:
|--projectDir/
| |--src/
| | |--main/
| | | |--java/
| | | |--resources/
The source files go in src/main/java
and the resource files go in src/main/resources
.
When you build your project Maven will compile the source files and put the .class
files in projectDir/target/classes
. It will also copy any resource files from src/main/resources
into target/classes
as well. By default, any non-source files in src/main/java
will be ignored. You mention you had put your styles.css
file in src/main/java
and thus it is not in target/classes
when you run your project.
When you execute the project the directory target/classes
(as well as any dependencies) is put on the classpath. But because your styles.css
file wasn't copied into target/classes
it is not included in the classpath and you end up getting NullPointerException
s when trying to reference it.
The solution is to move the styles.css
file into the src/main/resources
directory. It looks like you currently have it under src/main/java/rocks/zipcode/atm
. If you want to keep the resource file in the same "package" move it to src/main/resources/rocks/zipcode/atm
. Then it should be copied to the target/classes
directory when you rebuild your project.
Then you can reference it a couple of ways.
The first way is using getClass().getResource(...)
. If you pass an absolute path (starts with a /
) it will look for the resource from the root of the classpath. If you don't pass an absolute path (doesn't start with a /
) it will look for the resource relative to the Class
's location (in this case, the Class
is the one returned by getClass()
). This means, since you're getting the resource from within rocks.zipcode.atm.CacheMachineApp
, you could either do:
getClass().getResource("/rocks/zipcode/atm/styles.css")
, orgetClass().getResource("styles.css")
The other option is to use the behavior defined in the documentation of getStylesheets()
:
Gets an observable list of string URLs linking to the stylesheets to use with this scene's contents.
The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL does not have a [scheme:] component, the URL is considered to be the [path] component only. Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to the root of the application's classpath.
As you can see, when there is no scheme (e.g. file:
, https:
, etc.) it will look for the resource relative to the root of the the classpath. Since it is always relative to the root you need to pass the absolute path (but in this case it doesn't require a leading /
).
getStylesheets().add("rocks/zipcode/atm/styles.css");
Important Note: Make sure you get the path completely correct. It is case sensitive when packaged in a JAR file. It may or may not be case sensitive when not packaged in a JAR file, but that is dependent on the underlying file system. Either way, it is best to match the case of the actual path in order to avoid any problems.
Answered By - Slaw
Answer Checked By - Senaida (JavaFixing Volunteer)