Skip to content

(suggested): mvn test with JDK 17: several test/build issues and proposed fixes (FastByteBuffer, SystemProperties, OpenCmwConstants) #224

@zzeddo

Description

@zzeddo

Summary
When running mvn test -e on OpenCMW with JDK 17 (Windows environment) I encountered a set of issues that caused test failures and build interruptions. I investigated the causes, applied minimal, defensive code fixes, and verified module tests pass locally. This issue documents the reproduction steps, observed errors, root causes, the fixes I applied (with code excerpts), verification results.

Environment

  • OS: Windows 11 (cmd.exe default shell)
  • JDK: OpenJDK 17 (example used in my environment)
  • Maven: 3.x
  • Repo path used locally: D:\study\opencmw-java
  • Modules present: core, serialiser, server, server-rest, client, concepts, etc.

How to reproduce (local)
From repository root:

cd D:\study\opencmw-java

# Run full project tests (may take some time)
mvn test -e

To test focused modules (faster, for debugging):

# serialiser module only
mvn -pl serialiser test -e

# core module only
mvn -pl core test -e

# debug logging for core module
mvn -pl core test -e -X

Observed errors
I observed several distinct runtime/build problems while running tests under JDK 17:

  1. serialiser: static initializer attempted to touch jdk.internal.module.IllegalAccessLogger which caused initialization problems on some JVMs.
  • Symptom: Exception in static init of classes in serialiser (related to Unsafe / IllegalAccessLogger).
  1. core: resource path returned by URL#getPath() during tests started with /D:/... (Windows), causing Paths.get(configFile) to throw:
  • java.nio.file.InvalidPathException: Illegal char <:> at index 2: /D:/...
  1. core: attempts to obtain local host/IP using network internals triggered sun.nio.ch.Net initialization problems on some JVMs:
  • Symptoms: java.lang.NoClassDefFoundError: Could not initialize class sun.nio.ch.Net and related ExceptionInInitializerError.
  • This surfaced in tests that call OpenCmwConstants.getLocalHostName() or related network utilities.
  1. Module system warnings (not fatal but noisy): filename-based automodules such as jsoniter-0.9.23.jar show warnings about automatic modules.

Root cause analysis
A combination of JVM internals usage and platform-specific path formats caused failures:

  • Code in FastByteBuffer statically accessed internal JVM symbols in a brittle way; this can fail on some JDK builds.
  • Test resource handling used URL#getPath() without normalizing Windows-style returned paths that may include a leading slash before the drive letter.
  • Local-host detection relied solely on a DatagramSocket method that, on some JDK implementations/environments, triggers sun.nio.ch.Net initialization issues; code was not resilient to that failure.

Fixes applied (minimal, defensive changes)
I made three small, targeted changes to make code more defensive across JVM variants and Windows paths. The changes are intentionally minimal so they can be reviewed and merged as a quick patch / hotfix.
1) FastByteBuffer (serialiser)
File:serialiser/src/main/java/io/opencmw/serialiser/spi/FastByteBuffer.java
Change summary:

  • Obtain sun.misc.Unsafe first in a safe try/catch.
  • Attempt to silence jdk.internal.module.IllegalAccessLogger only if it exists; ignore ClassNotFoundException / NoSuchFieldException rather than failing initialization.

Core excerpt:

private static final Unsafe unsafe;
static {
    Unsafe tmpUnsafe = null;
    try {
        final Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        tmpUnsafe = (Unsafe) field.get(null);
        // try to silence the IllegalAccessLogger when present (some JVMs)
        try {
            Class<?> cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
            Field logger = cls.getDeclaredField("logger");
            tmpUnsafe.putObjectVolatile(cls, tmpUnsafe.staticFieldOffset(logger), null);
        } catch (ClassNotFoundException | NoSuchFieldException ignored) {
            // not present on this JVM, continue normally
        }
    } catch (SecurityException | IllegalAccessException | NoSuchFieldException e) {
        throw new SecurityException(e);
    }
    unsafe = tmpUnsafe;
}

Rationale:

  • Prevents static initializer from failing if jdk.internal.module.IllegalAccessLogger is not present (varies across JVM distributions and versions).

2) SystemProperties config path normalization (core)
File:core/src/main/java/io/opencmw/utils/SystemProperties.java
Change summary:

  • When loading config files, detect Windows "/D:/..." style paths and strip the leading slash before calling Paths.get(...).

Core excerpt:

final Properties configFileProperties = new Properties();
try {
    // handle Windows resource paths that may look like '/D:/path/to/file' coming from URL#getPath()
    String cfgPath = configFile;
    if (cfgPath.length() > 2 && cfgPath.charAt(0) == '/' && Character.isLetter(cfgPath.charAt(1)) && cfgPath.charAt(2) == ':') {
        cfgPath = cfgPath.substring(1);
    }
    try (InputStream input = Files.newInputStream(Paths.get(cfgPath))) {
        configFileProperties.load(input);
    }
} catch (final IOException e) {
    if (!DEFAULT_CFG.equals(configFile)) {
        throw new IllegalArgumentException("could not find file: '" + Paths.get(configFile) + "'", e);
    }
    // fallback behavior for missing default.cfg
}

Rationale:

  • Fixes InvalidPathException on Windows when resource paths are used in tests.

3) OpenCmwConstants.getLocalHostName() defensive fallback (core)
File:core/src/main/java/io/opencmw/OpenCmwConstants.java

Change summary:

  • Keep DatagramSocket method but catch any exception and fall back to InetAddress.getLocalHost().
  • If that also fails, return "localhost" to make tests robust.
  • Ensure all control paths return a string (fixing compilation complaint about missing return).

Core excerpt:

public static String getLocalHostName() {
    String ip;
    try (DatagramSocket socket = new DatagramSocket()) {
        try {
            socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
            if (socket.getLocalAddress() == null) {
                throw new UnknownHostException("bogus exception can be ignored");
            }
            ip = socket.getLocalAddress().getHostAddress();
            if (ip != null) {
                return ip;
            }
            return "localhost";
        } catch (final Exception e) {
            // fallback: try InetAddress.getLocalHost()
            try {
                final InetAddress localhost = InetAddress.getLocalHost();
                if (localhost != null && localhost.getHostAddress() != null) {
                    return localhost.getHostAddress();
                }
            } catch (final Exception ex) {
                return "localhost";
            }
        }
    } catch (final Exception e) {
        return "localhost";
    }
    // defensive fallback
    return "localhost";
}

Rationale:

  • Avoids letting platform/JVM-specific network internals cause exceptions that make tests fail.

Verification / Test results (local)
After applying the changes locally:

  • Serialiser module:
mvn -pl serialiser test -e

Result: Tests run: 128, Failures: 0, Errors: 0

  • Core module:
mvn -pl core test -e

Result: Tests run: 150, Failures: 0, Errors: 0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions