Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
jre: [ 17, 21, 25, 26 ] # Test on all supported LTS and latest JDK.
jre: [ 17, 21, 25, 26, 27 ] # Test on all supported LTS and latest JDK.
include:
- os: windows-latest
jre: 26 # Test on the latest JDK for speeds.
Expand Down Expand Up @@ -77,7 +77,7 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
jre: [ 17, 21, 25, 26 ] # Test on all supported LTS and latest JDK.
jre: [ 17, 21, 25, 26, 27 ] # Test on all supported LTS and latest JDK.
include:
- os: windows-latest
jre: 26 # Test on the latest JDK for speeds.
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Changes
- Bump default Eclipse JDT formatter version from `4.40` to `4.41`.
- Java formatters (google-java-format, palantir-java-format, cleanthat) no longer rely on a hardcoded list of `com.sun.*` internal packages. The required packages are discovered in the `jdk.compiler` module at runtime, which improves support for newer JDKs such as JDK 27. ([#3128](https://github.com/diffplug/spotless/issues/3128))

## [4.10.3] - 2026-09-25

Expand Down
88 changes: 31 additions & 57 deletions lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,30 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.Jvm;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import sun.misc.Unsafe;

/**
* Some Java formatters (google-java-format, palantir-java-format, cleanthat) need access to
* the internal packages of the JDK compiler module. Instead of hardcoding those package names,
* which are JDK internals that may be renamed or removed, we enumerate and open every package
* of that module.
*/
final class ModuleHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleHelper.class);

/** The module which owns the packages that the Java formatters above require. */
private static final String REQUIRED_MODULE = "jdk.compiler";

// prevent direct instantiation
private ModuleHelper() {}

private static final Map<String, String> REQUIRED_PACKAGES_TO_TEST_CLASSES = new HashMap<>();

static {
REQUIRED_PACKAGES_TO_TEST_CLASSES.putIfAbsent("com.sun.tools.javac.util", "Context");
REQUIRED_PACKAGES_TO_TEST_CLASSES.putIfAbsent("com.sun.tools.javac.file", "CacheFSInfo");
REQUIRED_PACKAGES_TO_TEST_CLASSES.putIfAbsent("com.sun.tools.javac.tree", "TreeTranslator");
REQUIRED_PACKAGES_TO_TEST_CLASSES.putIfAbsent("com.sun.tools.javac.parser", "Tokens$TokenKind");
REQUIRED_PACKAGES_TO_TEST_CLASSES.putIfAbsent("com.sun.tools.javac.api", "DiagnosticFormatter$PositionKind");
}

private static boolean checkDone;

public static synchronized void doOpenInternalPackagesIfRequired() {
Expand All @@ -67,9 +61,8 @@ public static synchronized void doOpenInternalPackagesIfRequired() {
if (!failedToOpen.isEmpty()) {
final StringBuilder message = new StringBuilder();
message.append("WARNING: Some required internal classes are unavailable. Please consider adding the following JVM arguments\n");
message.append("WARNING: ");
for (String name : failedToOpen) {
message.append("--add-opens jdk.compiler/%s=ALL-UNNAMED".formatted(name));
message.append("WARNING: --add-opens %s/%s=ALL-UNNAMED%n".formatted(REQUIRED_MODULE, name));
}
LOGGER.warn("{}", message);
}
Expand All @@ -79,34 +72,31 @@ public static synchronized void doOpenInternalPackagesIfRequired() {
}
}

@SuppressFBWarnings("REC_CATCH_EXCEPTION") // workaround JDK11
/** @return the packages of {@link #REQUIRED_MODULE} which are not open to the module calling this code. */
private static List<String> unavailableRequiredPackages() {
final Module callerModule = ModuleHelper.class.getModule();
final List<String> packages = new ArrayList<>();
for (Map.Entry<String, String> e : REQUIRED_PACKAGES_TO_TEST_CLASSES.entrySet()) {
final String key = e.getKey();
final String value = e.getValue();
try {
final Class<?> clazz = Class.forName(key + "." + value);
if (clazz.isEnum()) {
clazz.getMethod("values").invoke(null);
} else {
clazz.getDeclaredConstructor().newInstance();
for (Module module : requiredModules()) {
for (String name : module.getPackages()) {
if (!module.isOpen(name, callerModule)) {
packages.add(name);
}
} catch (IllegalAccessException ex) {
packages.add(key);
} catch (Exception ignore) {
// in old versions of JDK some classes could be unavailable
}
}
return packages;
}

@SuppressWarnings("unchecked")
private static void openPackages(Collection<String> packagesToOpen) throws Throwable {
final Collection<?> modules = allModules();
if (modules == null) {
return;
private static List<Module> requiredModules() {
final List<Module> modules = new ArrayList<>();
for (Module module : ModuleLayer.boot().modules()) {
if (REQUIRED_MODULE.equals(module.getName())) {
modules.add(module);
}
}
return modules;
}

private static void openPackages(Collection<String> packagesToOpen) throws Throwable {
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
Expand All @@ -115,30 +105,14 @@ private static void openPackages(Collection<String> packagesToOpen) throws Throw
unsafe.staticFieldBase(implLookupField),
unsafe.staticFieldOffset(implLookupField));
final MethodHandle modifiers = lookup.findSetter(Method.class, "modifiers", Integer.TYPE);
final Method exportMethod = Class.forName("java.lang.Module").getDeclaredMethod("implAddOpens", String.class);
modifiers.invokeExact(exportMethod, Modifier.PUBLIC);
for (Object module : modules) {
final Collection<String> packages = (Collection<String>) module.getClass().getMethod("getPackages").invoke(module);
for (String name : packages) {
final Method addOpensMethod = Module.class.getDeclaredMethod("implAddOpens", String.class);
modifiers.invokeExact(addOpensMethod, Modifier.PUBLIC);
for (Module module : requiredModules()) {
for (String name : module.getPackages()) {
if (packagesToOpen.contains(name)) {
exportMethod.invoke(module, name);
addOpensMethod.invoke(module, name);
}
}
}
}

@Nullable @SuppressFBWarnings("REC_CATCH_EXCEPTION") // workaround JDK11
private static Collection<?> allModules() {
// calling ModuleLayer.boot().modules() by reflection
try {
final Object boot = Class.forName("java.lang.ModuleLayer").getMethod("boot").invoke(null);
if (boot == null) {
return null;
}
final Object modules = boot.getClass().getMethod("modules").invoke(boot);
return (Collection<?>) modules;
} catch (Exception ignore) {
return null;
}
}
}
1 change: 1 addition & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Changes
- Bump default `eclipse` version from `4.40` to `4.41`.
- No longer hardcode the `com.sun.*` internal packages required by the Java formatters, improving support for newer JDKs such as JDK 27. ([#3128](https://github.com/diffplug/spotless/issues/3128))

## [8.10.3] - 2026-09-25
### Changes
Expand Down
1 change: 1 addition & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Changes
- Bump default `eclipse` version from `4.40` to `4.41`.
- No longer hardcode the `com.sun.*` internal packages required by the Java formatters, improving support for newer JDKs such as JDK 27. ([#3128](https://github.com/diffplug/spotless/issues/3128))

## [3.10.3] - 2026-09-25
### Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,8 @@ public class MavenIntegrationHarness extends ResourceHarness {
void gitAttributes() throws IOException {
setFile(".gitattributes").toContent("* text eol=lf");
if (Jvm.version() >= 16) {
// for GJF https://github.com/diffplug/spotless/issues/834
setFile(".mvn/jvm.config").toContent(
"--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"
+ " --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED"
+ " --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED"
+ " --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED"
+ " --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"
// this last line is for Detekt
+ // this last line is for Detekt
" --add-opens java.base/java.lang=ALL-UNNAMED");
// this line is for Detekt
setFile(".mvn/jvm.config").toContent("--add-opens java.base/java.lang=ALL-UNNAMED");
}
// copy the mvnw resources
copy("mvnw").setExecutable(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ import com.example.Test.Nested;
import com.example.a.A;
import com.example.b;
import com.example.c.C;
import com.sun.jna.platform.win32.COM.Unknown;
import com.sun.jna.platform.win32.Guid.CLSID;
import com.jna.platform.win32.COM.Unknown;
import com.jna.platform.win32.Guid.CLSID;
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ import com.example.Test.Nested;
import com.example.b;
import com.example.a.A;
import com.example.c.C;
import com.sun.jna.platform.win32.Guid.CLSID;
import com.sun.jna.platform.win32.COM.Unknown;
import com.jna.platform.win32.Guid.CLSID;
import com.jna.platform.win32.COM.Unknown;
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import com.example.Test.Nested;
import com.example.Test;
import com.example.Test.*;

import com.sun.jna.platform.win32.COM.Unknown;
import com.sun.jna.platform.win32.Guid.CLSID;
import com.jna.platform.win32.COM.Unknown;
import com.jna.platform.win32.Guid.CLSID;

import com.example.a.A;
import com.example.b;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void lexicographicSort() {

@Test
void semanticSort() {
FormatterStep step = ImportOrderStep.forJava().createFrom(false, true, Set.of("com.sun.jna.platform.win32.COM"),
FormatterStep step = ImportOrderStep.forJava().createFrom(false, true, Set.of("com.jna.platform.win32.COM"),
Set.of("com.example.b"), createTestFile("java/importsorter/import.properties"));
StepHarness.forStep(step).testResource("java/importsorter/JavaCodeUnsortedSemanticSort.test", "java/importsorter/JavaCodeSortedSemanticSort.test");
}
Expand Down