--add-opens VM args incomplete for modular projects when running a multi-class test suite
Description
When running a test suite at the module level on a JPMS-modular project, VS Code Java Test only generates --add-opens for one package (the first test class selected), leaving all other packages in the module closed to the JUnit runtime. This causes InaccessibleObjectException failures that do not occur when running the same suite in Eclipse IDE.
Steps to reproduce
- Have a Maven multi-module project with JPMS (
module-info.java in the module under test).
- In VS Code Test Explorer, run all tests at the module level (click the ▶ button next to the module node).
- Observe that some tests fail with:
java.lang.reflect.InaccessibleObjectException:
Unable to make ... accessible: module com.example.mymodule does not opens com.example.mymodule.somepackage to unnamed module
- Run the exact same suite in Eclipse IDE → all tests pass.
Root cause
The issue is in JUnitLaunchConfigurationDelegate.java.
The launch argument resolution follows this sequence:
Step 1 – getJUnitLaunchArguments calls the parent JDT delegate's getVMRunnerConfiguration via reflection (acknowledged by the TODO comment in the code):
// TODO: Make the getVMRunnerConfiguration() in super class protected.
final Method getVMRunnerConfiguration = getClass().getSuperclass().getDeclaredMethod(
"getVMRunnerConfiguration", ILaunchConfiguration.class, ILaunch.class,
String.class, IProgressMonitor.class);
getVMRunnerConfiguration.invoke(this, configuration, launch, mode, new NullProgressMonitor());
The parent's collectExecutionArguments runs here. It generates --add-opens com.example.mymodule/<package>=ALL-UNNAMED for each test element in fTestElements. However, at this point the JDT configuration only knows about one class — the "main type" derived from testNames[0] in JUnitLaunchUtils.resolveLaunchArgument. So --add-opens is emitted for exactly one package.
Step 2 – parseParameters strips out the test-selection arguments that the parent produced (-test, -testNameFile, -classNames, -packageNameFile):
private String[] parseParameters(String[] programArguments) throws CoreException {
for (int i = 0; i < programArguments.length; i++) {
if (testNameArgs.contains(programArguments[i])) {
// skip this argument and its value(s)
while (i + 1 < programArguments.length && !programArguments[i + 1].startsWith("-")) {
i++;
}
} else {
arguments.add(programArguments[i]);
...
}
}
addTestItemArgs(arguments); // re-injects the full class list
}
addTestItemArgs re-injects the complete list of test classes (all selected classes in the module) into -testNameFile. This is correct — the test runner now receives all classes.
Step 3 – The --add-opens VM args were already frozen in Step 1 and are not updated. The full class list added in Step 2 contains classes from many different packages, all of which remain closed.
Result: The JUnit engine can run the first class (its package was opened), but fails with InaccessibleObjectException as soon as it tries to reflectively access any class from a different package.
Why Eclipse IDE does not have this problem
Eclipse IDE uses the upstream JDT JUnitLaunchConfigurationDelegate.collectExecutionArguments directly, which iterates all test elements and emits --add-opens for each package before launching. It also generates more --add-reads directives. There is no two-phase "generate for one class, then replace the list" pattern.
Workaround
Manually add --add-opens for every package in the module to java.test.config.vmArgs in .vscode/settings.json:
"java.test.config": [
{
"name": "my-config",
"vmArgs": [
"--add-modules", "ALL-MODULE-PATH",
"--add-opens", "com.example.mymodule/com.example.mymodule.packagea=ALL-UNNAMED",
"--add-opens", "com.example.mymodule/com.example.mymodule.packageb=ALL-UNNAMED"
]
}
]
Expected behavior
When running a multi-class or module-level test suite, --add-opens should be generated for every package of every test class in the selection, not only for the first class used to set up the JDT launch configuration.
Proposed fix direction
After addTestItemArgs populates the full test class list, re-derive the set of packages from this.args.testNames and append the missing --add-opens entries to vmArguments. Alternatively, expose getVMRunnerConfiguration as protected in the upstream JDT delegate (the TODO comment already flags this) so that the full fTestElements can be populated correctly before collectExecutionArguments runs.
Environment
- Extension:
vscjava.vscode-java-test (replace with your installed version)
- Java extension:
redhat.java
- Project type: Maven multi-module, JPMS (
module-info.java present)
- JDK: 21
--add-opensVM args incomplete for modular projects when running a multi-class test suiteDescription
When running a test suite at the module level on a JPMS-modular project, VS Code Java Test only generates
--add-opensfor one package (the first test class selected), leaving all other packages in the module closed to the JUnit runtime. This causesInaccessibleObjectExceptionfailures that do not occur when running the same suite in Eclipse IDE.Steps to reproduce
module-info.javain the module under test).Root cause
The issue is in
JUnitLaunchConfigurationDelegate.java.The launch argument resolution follows this sequence:
Step 1 –
getJUnitLaunchArgumentscalls the parent JDT delegate'sgetVMRunnerConfigurationvia reflection (acknowledged by the TODO comment in the code):The parent's
collectExecutionArgumentsruns here. It generates--add-opens com.example.mymodule/<package>=ALL-UNNAMEDfor each test element infTestElements. However, at this point the JDT configuration only knows about one class — the "main type" derived fromtestNames[0]inJUnitLaunchUtils.resolveLaunchArgument. So--add-opensis emitted for exactly one package.Step 2 –
parseParametersstrips out the test-selection arguments that the parent produced (-test,-testNameFile,-classNames,-packageNameFile):addTestItemArgsre-injects the complete list of test classes (all selected classes in the module) into-testNameFile. This is correct — the test runner now receives all classes.Step 3 – The
--add-opensVM args were already frozen in Step 1 and are not updated. The full class list added in Step 2 contains classes from many different packages, all of which remain closed.Result: The JUnit engine can run the first class (its package was opened), but fails with
InaccessibleObjectExceptionas soon as it tries to reflectively access any class from a different package.Why Eclipse IDE does not have this problem
Eclipse IDE uses the upstream JDT
JUnitLaunchConfigurationDelegate.collectExecutionArgumentsdirectly, which iterates all test elements and emits--add-opensfor each package before launching. It also generates more--add-readsdirectives. There is no two-phase "generate for one class, then replace the list" pattern.Workaround
Manually add
--add-opensfor every package in the module tojava.test.config.vmArgsin.vscode/settings.json:Expected behavior
When running a multi-class or module-level test suite,
--add-opensshould be generated for every package of every test class in the selection, not only for the first class used to set up the JDT launch configuration.Proposed fix direction
After
addTestItemArgspopulates the full test class list, re-derive the set of packages fromthis.args.testNamesand append the missing--add-opensentries tovmArguments. Alternatively, exposegetVMRunnerConfigurationasprotectedin the upstream JDT delegate (the TODO comment already flags this) so that the fullfTestElementscan be populated correctly beforecollectExecutionArgumentsruns.Environment
vscjava.vscode-java-test(replace with your installed version)redhat.javamodule-info.javapresent)