Search code examples
javaswinguser-interfacejframeembedded-resource

ImageIO Cannot Find File


I'm writing a program involving graphical interface and I'm using ImageIO to read an image into a BufferedImage (the variable starImg) in the code below:

public Star() {
    super();
    if (starImg == null) {
        try {
            starImg = ImageIO.read(new File("star.png"));
        } catch (IOException e) {
            System.out.println("There was an issue reading the file: " + e);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

Except the code always ends up outputting the IO file not found exception message, even though it shouldn't. My star.png file is in the same folder as my code (ran using Eclipse), I've made sure that it's a png and not a jpg, and tried other things like changing File to FileInputStream, so I can't seem to figure out why this image cannot be read. Maybe it has something to do with the fact that I'm using Eclipse, although I find this unlikely. I've looked at other answers relating to the same issue, but the solutions that solved their problems haven't solved mine.

If someone could point out the bug in my code, that would be greatly appreciated.


Solution

  • My star.png file is in the same folder as my code (ran using Eclipse)

    Well, there is your problem.

    new File("star.png") will look in the process current working directory.

    Where is that? Who knows! - it's up to the user; where-ever the app was started from. Thus, this is broken as a concept: There is absolutely no guarantee that it is the right place.

    It gets worse - java apps are shipped as jars or jmods, and the files they need are enclosed within. Entries in jar files or jmod files are not files. new File() cannot possibly read these.

    The conclusion is that this process (make a file object, pass it to ImageIO.read) cannot work for resources.

    The solution is to ask the java classloading system to give you resources from where-ever the VM is loading classes from. If that's from disk, great, get em from there. If that's from within a jar or jmod file, cool, get em from there.

    This is how you do that:

    URL url = Star.class.getResource("star.png");
    
    // - OR -
    
    try (InputStream in = Star.class.getResourceAsStream("star.png")) {
       // use in here
    }
    

    The first one is useful whenever you have an API that accepts URL objects. I'm pretty sure ImageIO.read() is such an API.

    The second is useful for APIs that accept InputStreams. ImageIO.read also does that - if both are available, the URL based one is simpler (no need for a try-with-resources in that case).

    Note that this will look in the same place Star.class ended up, so in Star's 'package', so to speak. If you'd rather go from the 'root' folder (the root of the jar, for example), use e.g. "/img/star.png" instead; this will look in a dir named 'img' for the file, and that dir must be in the 'root'. So, if Star has package com.foo; at the top, there is some construct (jar file, jmod, etc) that has a com dir, which has a foo dir, which has a Star.class file. With "/img/star.png", there must be a dir named img, as sibling to the dir named com.

    Eclipse will automatically copy non-java files in src folders over, as an analogy: It 'copies' java files over by compiling them, and it 'compiles' non-java files by just copying them over. Hence, this works fine in eclipse, and also works fine with build systems (note that with maven, there is a dir structure to adhere to. sources go in src/main/java, non-java stuff goes in src/main/resources.