Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ClassLoader does not find resource files following build

I have been running into issues trying to direct the Java ClassLoader to retrieve JSON files from the test/resources directory following deployment.

public class TestFileUtil {
    private static final ClassLoader classLoader = TestFileUtil.class.getClassLoader();

    public static Map<String, Object> getJsonFileAsMap(String fileLocation) {

        try {
            return new ObjectMapper().readValue(getTestFile(fileLocation), HashMap.class);
        } catch (IOException e) {
            throw new RuntimeException("Error converting JSON file to a Map", e);
        }
    }

    private static File getTestFile(String fileLocation) {

        return new File(classLoader.getResource(fileLocation).getFile());
    }
}

The utility has no problems during local testing with Mockito like so:

public class LocalTest {

    @Before
    public void setUp() {
        Mockito.when(mockDataRetrievalService.getAssetJsonById(Mockito.any())).thenReturn(TestFileUtil.getJsonFileAsMap("test.json"));
    }
}

However this line throws a FileNotFound exception when building in our deployed environments.

When using a relative directory path "../../test.json", I am seeing FileNotFound exceptions in both environments.

Local directory structure:

test
| java
| |- project
| |  |- LocalTest
| |- util
| |  |- TestFileUtil.class
| resources
| |- test.json

After deployment:

test
| com
| | project
| | | dao
| | | | LocalTest
| | other project
| | | | util
| | | | | TestFileUtil.class
| | | | | test.json

Is there any special behavior or a required directory structure associated with using the ClassLoader in automated builds?

like image 496
Conor Avatar asked Sep 03 '25 08:09

Conor


1 Answers

The problem most likely is this:

new File(classLoader.getResource(fileLocation).getFile());

The getFile() method of the URL class does not return a valid file name. It just returns the path portion of the URL, which is not guaranteed to be a valid filename. (The method name made sense when the URL class was introduced as part of Java 1.0, since nearly all URLs did in fact refer to physical files, either on the same machine or on a different machine.)

The argument to ClassLoader.getResource is not a filename. It's a relative URL, whose base is each location in the ClassLoader’s classpath. If you want to read a resource bundled with your application, do not try to convert the resource URL to a file. Read the URL as a URL instead:

public class TestFileUtil {
    private static final ClassLoader classLoader = TestFileUtil.class.getClassLoader();

    public static Map<String, Object> getJsonFileAsMap(String fileLocation) {

        try {
            return new ObjectMapper().readValue(getTestFile(fileLocation), HashMap.class);
        } catch (IOException e) {
            throw new RuntimeException("Error converting JSON file to a Map", e);
        }
    }

    private static URL getTestFile(String fileLocation) {

        return classLoader.getResource(fileLocation);
    }
}

If you want to read a file that is not part of your application, don’t use getResource at all. Just create a File instance.

like image 200
VGR Avatar answered Sep 04 '25 23:09

VGR