apple/pkl · error · IllegalStateException
Package resolver has already been closed.
Error message
Package resolver has already been closed.
What it means
IllegalStateException thrown by checkNotClosed when a package resolver's API (getDependencyMetadata, listElements, hasElement) is used after the resolver was closed. Resolvers are one-shot resources; once closed, all further lookups are invalid.
Solutions
- Do not use the resolver after calling close(); obtain a fresh resolver instance for subsequent operations
- Restructure code so all resolver calls happen inside the try block of a try-with-resources statement
- Check for premature close() calls (e.g. in finally blocks or shutdown hooks) that run before remaining lookups
- Ensure concurrent code doesn't share one resolver across threads with differing lifetimes
Example fix
// before
var resolver = PackageResolvers.open(...);
resolver.close();
resolver.getDependencyMetadata(uri); // throws
// after
try (var resolver = PackageResolvers.open(...)) {
resolver.getDependencyMetadata(uri);
} Defensive patterns
Strategy: try-catch
Validate before calling
// guard before use
if (resolver.isClosed()) throw new IllegalStateException("resolver closed"); Type guard
boolean isUsable(PackageResolver r) { return r != null && !r.isClosed(); } Try / catch
try {
resolver.getDependencyMetadata(uri);
} catch (IllegalStateException e) {
if (e.getMessage().contains("already been closed")) {
resolver = reopenResolver(); // reacquire and retry once
}
} Prevention
- Use try-with-resources so close() only runs at scope exit
- Never store resolver references in long-lived fields
- Don't close a shared resolver while other tasks may still use it
- Keep resolver creation and use in the same method/scope
When it happens
Trigger: Calling getDependencyMetadata, listElements, or hasElement on a PackageResolver after close() was called on it (or on its parent ProjectPackageResolver / resolver manager).
Common situations: Holding a resolver reference beyond the scope that closed it; closing a resolver in a finally block and reusing it afterwards; sharing a resolver across concurrent tasks where one task closes it early.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/3535631f7f0f464f.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java:245
protected abstract List<PathElement> doListElements(
PackageAssetUri uri, @Nullable Checksums checksums)
throws IOException, SecurityManagerException;
protected abstract boolean doHasElement(PackageAssetUri uri, @Nullable Checksums checksums)
throws IOException, SecurityManagerException;
protected PackageLoadError invalidPackageZipUrl(
PackageUri packageUri, DependencyMetadata dependencyMetadata) {
return new PackageLoadError(
"invalidPackageZipUrl",
packageUri.getDisplayName(),
dependencyMetadata.getPackageZipUrl());
}
private void checkNotClosed() {
if (isClosed.get()) {
throw new IllegalStateException("Package resolver has already been closed.");
}
}
}
/**
* A package resolver that holds entire package contents in memory.
*
* <p>This gets used when the cache dir is not set.
*/
static final class InMemoryPackageResolver extends AbstractPackageResolver {
@GuardedBy("lock")
private final EconomicMap<PackageUri, EconomicMap<String, ByteBuffer>> cachedEntries =
EconomicMaps.create();
@GuardedBy("lock")
private final EconomicMap<PackageUri, TreePathElement> cachedTreePathElementRoots =
EconomicMaps.create();
View on GitHub (pinned to f3efcbfc9b)