oracle/graal · error · IOException

Resource {resource} not found from class loader: {loaderClas

Error message

Resource {resource} not found from class loader: {loaderClassName}

What it means

ServiceWatcher (hotswap agent support) resolves a resource name through a class loader to get a URL it can watch for changes. If the loader cannot find the resource, the URL is null and it throws IOException naming the resource and loader class; watching META-INF/services files is meaningless when the file does not exist.

Source

Thrown at espresso/src/com.oracle.truffle.espresso.hotswap/src/com/oracle/truffle/espresso/hotswap/ServiceWatcher.java:114

                    StandardWatchEventKinds.ENTRY_DELETE,
                    StandardWatchEventKinds.OVERFLOW};

    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    private final Map<String, Set<String>> services = new HashMap<>(4);
    private WatcherThread serviceWatcherThread;
    private URLWatcher urlWatcher;

    public boolean addResourceWatcher(ClassLoader loader, String resource, HotSwapAction callback) throws IOException {
        ensureInitialized();
        URL url;
        if (loader == null) {
            url = ClassLoader.getSystemResource(resource);
        } else {
            url = loader.getResource(resource);
        }
        if (url == null) {
            throw new IOException("Resource " + resource + " not found from class loader: " + loader.getClass().getName());
        }

        if ("file".equals(url.getProtocol())) {
            // parent directory to register watch on
            try {
                Path path = Paths.get(url.toURI());
                serviceWatcherThread.addWatch(path, () -> callback.fire());
                return true;
            } catch (URISyntaxException e) {
                return false;
            }
        }
        return false;
    }

    public synchronized void addServiceWatcher(Class<?> service, ClassLoader loader, HotSwapAction callback) throws IOException {
        ensureInitialized();
        // cache initial service implementations

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Verify the resource exists for that exact loader: loader.getResource(name) != null before registering the watcher.
  2. Fix the resource path spelling and ensure it is on the classpath/module path of the loader passed in.
  3. When shading, keep META-INF/services entries (e.g. ServicesResourceTransformer in Maven Shade).

Example fix

// before
watcher.addResourceWatcher(loader, "META-INF/services/com.acme.Api", cb); // may throw

// after
String res = "META-INF/services/com.acme.Api";
if (loader.getResource(res) != null) {
    watcher.addResourceWatcher(loader, res, cb);
} else {
    // log / skip watching
}
Defensive patterns

Strategy: validation

Validate before calling

URL u = (loader == null) ? ClassLoader.getSystemResource(res) : loader.getResource(res);
if (u == null) { /* skip or report; do not call addResourceWatcher */ }

Try / catch

try {
    watcher.addResourceWatcher(loader, res, cb);
} catch (IOException e) {
    if (e.getMessage().contains("not found from class loader")) { /* resource absent; skip watching */ }
}

Prevention

When it happens

Trigger: addResourceWatcher(loader, resource, callback) with a resource path that is misspelled, lives in a different module/classloader, or is packaged in a jar the loader cannot see (or is null loader -> system resource missing).

Common situations: HotSwap tooling watching 'META-INF/services/...' files that were stripped during shading; resources renamed between library versions; modularized apps where the service file sits in a non-visible layer.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/f2c0025a5640fa1d. Report an issue: GitHub.