The following code (to retrieve the base URL of a Java Web Start client application via the JNLP API) worked in Java 8 but failed when executed in (modularized) Java 9 runtime:
Class<?> mclass = Class.forName("javax.jnlp.ServiceManager");
Method lookup = mclass.getMethod("lookup", new Class[]{String.class});
Object basicSvc = lookup.invoke(null, new Object[{"javax.jnlp.BasicService"});
Class<?> sclass = basicSvc.getClass();
Method getCodeBase = sclass.getMethod("getCodeBase", (Class[])null);
URL codebase = (URL)getCodeBase.invoke(basicSvc, (Object[])null); // throws
Results in
java.lang.IllegalAccessException: class app.App cannot access class
com.sun.jnlp.BasicServiceImpl (in module jdk.javaws) because module
jdk.javaws does not export com.sun.jnlp to unnamed module @7202a0fa
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException
at java.base/java.lang.reflect.AccessibleObject.checkAccess
at java.base/java.lang.reflect.Method.invoke
at app.App.init
How can this be fixed?
As discussed in a previous more general question the problem is that the second reflection method is not defined by the public API class but the private implementation, which does not work since Java 9 accesibility rules apply.
The fix is to base the getCodeBase method against the public interface instead:
Class<?> sclass = Class.forName("javax.jnlp.BasicService");
This also avoids the reflection anti-pattern to work with dynamic defining classes.
Using a static implementation would also avoid the problem (however this has the problem that it requires the javaws.jar
which might not be easy to obtain in some build environments).
import javax.jnlp.BasicService;
import javax.jnlp.ServiceManager;
BasicService basicSvc = (BasicService)ServiceManager.lookup("javax.jnlp.BasicService");
URL u = basicSvc.getCodeBase();
Thanks to @Holger to check the reflection implementation and @Alan Bateman in guessing what the actual problem was without seeing the code. Seperated the two questions as suggested by @nicolai, which makes it much cleaner.