I want to display an Image in a JLabel by drawing a BufferedImage on it.
The x/yOffset is to draw a smaller image in the middel of the JLabel.
If I run the code in
The problem lies here:
new File(getClass().getResource(path).toURI())
An application resource is not guaranteed to be a separate file. A .jar entry is just part of a compressed archive. It’s not a separate file on the hard drive. That is why you cannot use File to read it.
The correct way to read a resource is by not attempting to convert it to a file at all. getResource
returns a URL; you can just pass that URL directly to the ImageIO.read method that takes a URL:
img = ImageIO.read(ImageHQ.class.getResource(path));
Note the use of a class literal, ImageHQ.class
, instead of getClass(). This guarantees that your resource is read relative to your own class, and not a subclass which might be in a different package or different module.
Generally speaking, there may be cases where a URL isn’t sufficient. You can also use getResourceAsStream to obtain an open InputStream that reads from the resource. In your case, you could do:
try (InputStream stream = ImageHQ.class.getResource(path)) {
img = ImageIO.read(stream);
}
But this would be sub-optimal, since a URL can provide information that an InputStream cannot, like a file name, content type, and advance knowledge of the image data length.
The String argument you pass to getResource
and getResourceAsStream
is not actually a file name. It’s the path portion of a relative URL. This means an argument that starts with, say, C:\
will always fail.
Because the argument is a URL, it always uses forward slashes (/
) to separate path components, on all platforms. Normally, it is resolved against the package of the class object whose getResource* method is called; so, if ImageHQ
were in the com.example
package, this code:
ImageHQ.class.getResource("logo.png")
would look for com/example/logo.png in the .jar file.
You can optionally start the String argument with a slash, which will force it to be relative to the root of the .jar file. The above could be written as:
ImageHQ.class.getResource("/com/example/logo.png")
There are also getResource* methods in ClassLoader, but those should not be used. Always use Class.getResource or Class.getResourceAsStream instead. The ClassLoader methods are functionally similar in Java 8 and earlier, but as of Java 9, Class.getResource is safer in modular programs because it won’t run afoul of module encapsulation. (ClassLoader.getResource does not allow /
at the start of its String argument, and always assumes the argument is relative to the root of a .jar file.)
All getResource* methods will return null
if the path argument does not name a resource that actually exists in the .jar file (or if the resource is in a module that doesn’t allow it to be read). A NullPointerException or IllegalArgumentException is a common symptom of this. For instance, if no logo.png
was in the same package as the ImageHQ class in the .jar file, getResource would return null, and passing that null to ImageIO.read
would result in an IllegalArgumentException, as stated in the ImageIO.read documentation.
If this occurs, you can troubleshoot the .jar file by listing its contents. There are a number of ways to do this:
jar tf /path/to/myapplication.jar
.unzip -v /path/to/myapplication.jar
will also work, since a .jar file is actually a zip file with a few Java-specific entries.Going back to the example, if your class were in the com.example
package and your code were doing ImageHQ.class.getResource("logo.png")
, you would check the contents of the .jar file for a com/example/logo.png
entry. If it’s not there, the getResource method will return null.
Replace ex.getMessage()
with ex.toString()
. It is often the case that an exception’s message is meaningless by itself. You should also add ex.printStackTrace();
to each catch
block (or add a logging statement that records the stack trace), so you’ll know exactly where the problem occurred.
Never call repaint()
from a paintComponent method. This creates an infinite loop, because repaint()
will force the Swing painting system to call paintComponent
again.