Search code examples
javareflectionjava-11java-17

java 17 - reflection to use methods in external jar files - Unable to make protected void java.net.URLClassLoader.addURL(java.net.URL) accessible


I've this code below which is used to load methods defined in some external jar files and in the web application using Spring Boot 2.7.10 version, it can invoke these external methods at run time.

This works fine with java 11, but when upgraded to java 17, it has error:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected void java.net.URLClassLoader.addURL(java.net.URL) accessible: module java.base does not "opens java.net" to unnamed module @221af3c0
    private static void addToClassPath(String jarFilePath) {
        try {
            File file = new File(jarFilePath);
            if (!(file.exists() && file.canRead())) {
                throw new UdfDirectoryNotAccessibleException(jarFilePath);
            }
            URL url = file.toURI().toURL();
            URLClassLoader urlClassLoader = (URLClassLoader) UdfInvoker.class.getClassLoader();
            Class urlClass = URLClassLoader.class;
            Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});

            // error here since java 17
            method.setAccessible(true);
            method.invoke(urlClassLoader, new Object[] { url });
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
            throw new UdfFailedLoadingException(jarFilePath, ex.getMessage(), ex);
        } catch (MalformedURLException ex) {
            throw new UdfJarFileNotAccessibleException(jarFilePath, ex.getMessage(), ex);
        }
    }

Does anyone know how this code can work with java 17 or if it can change to use something else which can work with both java 11 and java 17? Thanks.

NOTE:

  • Adding arguments to JDK is not an option as I cannot change that in servlet container Tomcat or JRE of development machine.
  • Downgrade java version to 11 is also not an option.

Solution

  • Thanks to VGR user, I managed to make it work with these steps below using ServiceLoader from JDK 1.6:

    First, in the project code I create a file call Service

    package test
    public interface Service {
        
        public Result handle(List<Result> paramList);
        
    }
    

    Then, I create a separated java file out of the project code which will be built as external java file

    package implement
    import test.Service;
    public class ServiceImp implements Service {
      
        public Result handle(List<Result> paramList) {
            // write code here
        }
    
    }
    
    

    Then, I build the jar file for implement.jar which contains SeviceImp.class.

    NOTE: in this jar file, it must contain this file path META-INF/services/test.Service and the file contains test.ServiceImp.

    In the project code, I have this method to load the given implement.jar file.

                URL[] urls = new URL[] { file.getAbsoluteFile().toURI().toURL()};
                ClassLoader parent = UdfInvoker.class.getClassLoader();
                URLClassLoader c = new URLClassLoader(urls, parent);
                
                ServiceLoader<Service> sl = ServiceLoader.load(Service.class, c);
                Iterator<Service> apit = sl.iterator();
                while (apit.hasNext()) {
                    // Here you can store it to a list to reuse later or call it directly 
                    apit.next().handle(new ArrayList<>());