JDK-8350547 : ClassLoader.definePackage() throws IllegalArgumentException if package already defined
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang:class_loading
  • Priority: P4
  • Status: Draft
  • Resolution: Unresolved
  • Submitted: 2025-02-23
  • Updated: 2025-02-23
Related Reports
CSR :  
Description
Summary
-------

`ClassLoader.definePackage` to be changed to avoid custom class loaders having to deal with `IllegalArgumentExceptions` due to concurrent calls to `definePackage()` (loading two classes in the same package concurrently).

Problem
-------

Concurrent calls to `definePackage()` can yield `IllegalArgumentException`s if the package is already defined. Some built-in class loaders, like `URLClassLoader`, already handle this case, but custom class loaders (would) all have to implement the handling of concurrent class loading.

The CDI reference implementation's default behavior (Weld) [1] and Quarkus [2] are known to run into IAEs due to concurrent calls to `definePackage()`.

Solution
--------

Change the implementation of `CL.definePackage()` to only throw an `IllegalArgumentException` if the given package properties are incompatible with the already existing one.

Properties to consider for the "compatible check" are those defined in `Package` and its super class `NamedPackage`:

* `specTitle`, `specVersion`, `specVendor`, `implTitle`, `implVersion`, `implVendor` (via `.equals()`)
* `module` (object identity comparison)
* `sealBase` via `Package.isSealed()` and `Package.isSealed(URL)`

The core implementation change in `java.lang.ClassLoader` is to functionally replace the check around `packages.putIfAbsent()` from
```
    protected Package definePackage(String name, String specTitle,
                                 implTitle, implVersion, implVendor,
                                 sealBase, this);
...
        Package p = new Package(name, specTitle, specVersion, specVendor,
                                implTitle, implVersion, implVendor,
                                sealBase, this);

       if (packages.putIfAbsent(name, p) != null)
           throw new IllegalArgumentException(name);
       return p;
```
to
```
...
       NamedPackage ex = packages.putIfAbsent(name, p);
       if (ex == null)
           return p;
       // '.equals' here for simplicity for the CSR.
       // Implementation to check the individual properties
       // and use 'isSealed(URL)'.
       if (ex.equals(p))
           return (Package) ex;
       throw new IllegalArgumentException(
           "Incompatible redefinition of package " + name);
     }
```

Additionally, the thrown IAE's message shall indicate the package name and a description.

The existing special handling in `BuiltinClassLoader` and `URLClassLoader` can be removed.

Specification
-------------

```
--- a/src/java.base/share/classes/java/lang/ClassLoader.java
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java
@@ -2036,14 +2036,19 @@ private Package toPackage(String name, NamedPackage p, Module m) {
      *         respect to the given code source {@link java.net.URL URL}
      *         object.  Otherwise, the package is not sealed.
      *
-     * @return  The newly defined {@code Package} object
+     * @return  The {@code Package} object for the given implementation
+     *          and specification title/version/vendor and seal-base
+     *          properties.
      *
      * @throws  NullPointerException
      *          if {@code name} is {@code null}.
      *
      * @throws  IllegalArgumentException
      *          if a package of the given {@code name} is already
-     *          defined by this class loader
+     *          defined by this class loader with non-equal values for
+     *          the package properties (implementation and specification
+     *          version, vendor, title) or a different {@code sealBase}
+     *          or module.
      *
      *
      * @since  1.2
```

References
----------

[1] https://github.com/weld/core/blob/d55d91bec804b0c42fd8db38f74a93b99b6c541c/impl/src/main/java/org/jboss/weld/bootstrap/ConcurrentBeanDeployer.java

[2] https://github.com/quarkusio/quarkus/issues/37363