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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions