Skip to content

[ISSUE #10612] Allow controller startup on JDK 17 via --add-opens#10638

Open
123123213weqw wants to merge 1 commit into
apache:developfrom
123123213weqw:wangyue/issue-10612
Open

[ISSUE #10612] Allow controller startup on JDK 17 via --add-opens#10638
123123213weqw wants to merge 1 commit into
apache:developfrom
123123213weqw:wangyue/issue-10612

Conversation

@123123213weqw

@123123213weqw 123123213weqw commented Jul 21, 2026

Copy link
Copy Markdown

What

Allows the controller path (both controller-in-namesrv and the standalone mqcontroller) to start on JDK 17 by adding --add-opens java.base/java.lang=ALL-UNNAMED to the JDK 9+ branch of runserver.sh.

Why

Closes #10612. Also closes #10170 (duplicate, same root cause).

On JDK 9+, the controller fails at startup with InaccessibleObjectException:

java.lang.reflect.InaccessibleObjectException: Unable to make field private
java.lang.String java.lang.StackTraceElement.classLoaderName accessible:
module java.base does not "opens java.lang" to unnamed module @...
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    ...
    at com.caucho.hessian.io.JavaDeserializer.getFieldMap(JavaDeserializer.java:329)
    at com.caucho.hessian.io.JavaDeserializer.<init>(JavaDeserializer.java:97)
    at com.caucho.hessian.io.StackTraceElementDeserializer.<init>(StackTraceElementDeserializer.java:57)
    at com.caucho.hessian.io.SerializerFactory.<clinit>(SerializerFactory.java:589)
    at org.apache.rocketmq.controller.impl.manager.ReplicasInfoManager.<clinit>(ReplicasInfoManager.java:79)
    at org.apache.rocketmq.controller.impl.DLedgerController.<init>(DLedgerController.java:132)
    at org.apache.rocketmq.controller.ControllerManager.initialize(ControllerManager.java:122)

The Hessian SerializerFactory (pulled in transitively via DLedger, used by ReplicasInfoManager for brokerReplicaInfo serialization) reflectively accesses java.lang.StackTraceElement.classLoaderName during its static initializer. JDK 9+ strong encapsulation denies that access by default.

The user-confirmed workaround in #10170 is precisely this JVM flag; this PR puts it in the right branch of the shipped script so users don't have to roll their own.

How

runserver.sh already detects JAVA_MAJOR_VERSION and branches at line 87. This PR adds the --add-opens to the else (JDK 9+) branch only, so JDK 8 — which doesn't recognize the option — is untouched.

    else
      # ... existing G1GC + Xlog options ...
      # JDK 9+ strong encapsulation blocks the reflective access that the
      # Hessian SerializerFactory (pulled in transitively via DLedger, used on
      # the controller path) performs against JDK core types ...
      JAVA_OPT="${JAVA_OPT} --add-opens java.base/java.lang=ALL-UNNAMED"
    fi

Scope is deliberately narrow: only java.base/java.lang, because that's the only package hessian's StackTraceElementDeserializer reaches into. If hessian (or anything else) later trips on a different JDK core package, it's a one-line addition here.

Verification

Couldn't run the full controller on the test box (no maven), so I verified the mechanism directly with a minimal reproducer that performs the same reflective access:

import java.lang.reflect.Field;
public class TestAddOpens {
    public static void main(String[] args) throws Exception {
        Field f = StackTraceElement.class.getDeclaredField("classLoaderName");
        f.setAccessible(true);
        System.out.println("OK: " + f.get(new StackTraceElement("a","b","c",1)));
    }
}

Results on the test box (Ubuntu 22.04, OpenJDK 17.0.19 + OpenJDK 1.8.0_492):

JDK Command Result
17 java TestAddOpens InaccessibleObjectException (matches the issue's stack trace)
17 java --add-opens java.base/java.lang=ALL-UNNAMED TestAddOpens OK: null
8 java --add-opens java.base/java.lang=ALL-UNNAMED TestAddOpens Unrecognized option: --add-opens (confirms the JDK 9+ branch guard is required)

bash -n runserver.sh clean.

Notes for reviewers

Scope: which startup paths does this affect?

The reflective Hessian access (com.caucho.hessian.io.SerializerFactory.<clinit>) is specific to the controller path. grep -rln com.caucho --include='*.java' returns exactly one file in the whole tree: controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java.

Start script Used by Uses Hessian? Affected by this PR?
runserver.sh mqnamesrv (incl. controller-in-namesrv), mqcontroller, mqproxy only controller does yes — proxy inherits the flag but it's a no-op for it (JDK silently ignores opens for packages the process never reflects into)
runbroker.sh mqbroker, mqbrokercontainer no no — no change needed

So this PR fully closes #10612 and the duplicate #10170. It is not claiming to fix a Hessian issue on broker/proxy, because there isn't one.

  • I went with the script-level fix instead of a code-level fix (subclassing SerializerFactory and overriding getClassDeserializer) because:
    1. It matches the workaround the project already endorses ([Bug] Does not support JDK 17 #10170 comment from @Kris20030907).
    2. It's a one-line, zero-risk change.
    3. The same runserver.sh is used by both startup paths, so a single change covers both.
  • If you'd prefer a code-level fix instead (or in addition), I'm happy to do that — the SerializerFactory could be wrapped to skip the StackTraceElementDeserializer registration, which would also let us drop the JVM flag eventually.
  • .cmd (runserver.cmd) doesn't have the equivalent fix — let me know if you want it patched in this PR or separately; I held off because I can't verify the Windows behavior here.

How to test

  1. Build as usual, then on a JDK 17 box:
    sh bin/mqcontroller -c ...   # or bin/mqnamesrv with enableControllerInNamesrv=true
    
  2. Before this PR: InaccessibleObjectException from SerializerFactory.<clinit>.
  3. After this PR: starts normally.

On JDK 9+ the controller path (both controller-in-namesrv and the
standalone mqcontroller) fails at startup with
InaccessibleObjectException, because the Hessian SerializerFactory
(pulled in transitively via DLedger, used by ReplicasInfoManager)
reflectively accesses java.lang.StackTraceElement.classLoaderName
during its <clinit>, which JDK 9+ strong encapsulation denies.

Add `--add-opens java.base/java.lang=ALL-UNNAMED` to the JDK 9+ branch
of runserver.sh (the same script both startup paths go through). The
JDK < 9 branch is untouched, since `--add-opens` is an unrecognized
option on Java 8.

Verified the fix end-to-end with a minimal reproducer that performs the
same reflective access:
- JDK 17 without --add-opens: InaccessibleObjectException (matches the
  stack trace in apache#10612 / apache#10170)
- JDK 17 with --add-opens: reflective access succeeds
- JDK 8 with --add-opens: "Unrecognized option" (confirms the JDK 9+
  branch guard is required)

Also fixes apache#10170 (duplicate of the same root cause).
@itxaiohanglover

Copy link
Copy Markdown

Nice minimal fix. One question for scope confirmation: does the same --add-opens java.base/java.lang=ALL-UNNAMED need to apply to the broker/proxy start paths, or is the reflective access into java.lang specific to the controller path here? A one-line note in the PR body on scope would help reviewers confirm this also fully closes the duplicate #10170.

@123123213weqw

Copy link
Copy Markdown
Author

@itxaiohanglover good question — I should have called this out in the body. The reflective access is specific to the controller path:

  • Hessian is used in exactly one place in the whole tree: controller/.../ReplicasInfoManager.java (grep -rln com.caucho --include='*.java' returns only that file).
  • broker starts via runbroker.sh, doesn't touch Hessian → no change needed there.
  • proxy starts via runserver.sh and so inherits this --add-opens, but doesn't actually use Hessian either. The extra flag is a no-op for the proxy on JDK 9+ (the JVM silently ignores opens for packages the process never reflects into), so it's safe to share.

So the scope of this PR is "fix controller startup on JDK 17 via the shared runserver.sh"; it doesn't claim to fix a Hessian issue on broker/proxy because there isn't one. The duplicate #10170 reports the exact same SerializerFactory.<clinit> failure and is fully closed by this change.

I'll add a one-line scope note to the PR body to make that explicit for future readers.

@RockteMQ-AI RockteMQ-AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by github-manager-bot

Summary

Adds --add-opens java.base/java.lang=ALL-UNNAMED to the JDK 9+ branch of runserver.sh, fixing InaccessibleObjectException during controller startup on JDK 17. Closes #10612 and #10170.

Findings

  • [Info] distribution/bin/runserver.sh:96-102 — The 7-line comment is thorough and well-justified. It clearly traces the root cause: Hessian SerializerFactory → DLedger → ReplicasInfoManager → reflective access to StackTraceElement.classLoaderName. This makes future maintainers' lives much easier.
  • [Info] distribution/bin/runserver.sh:103 — The scope is deliberately narrow (java.base/java.lang only), which is the right call. If other JDK packages need opening later, it's a one-line addition.
  • [Info] distribution/bin/runserver.sh:93 — Correctly placed inside the else (JDK 9+) branch, so JDK 8 users are completely unaffected. Good defensive placement.

Suggestions

  • Consider adding the equivalent --add-opens to runserver.cmd (Windows) as a follow-up. The PR body already acknowledges this gap, so a tracking issue or follow-up PR would close the loop for Windows users.

Verdict

Clean, minimal, well-documented fix. The PR body is excellent — clear problem statement, root cause chain, and verification table. LGTM.


Automated review by github-manager-bot

@123123213weqw

Copy link
Copy Markdown
Author

@itxaiohanglover addressed — added a "Scope" section to the PR body with a table covering broker/proxy/controller/startup-script, plus the grep result showing Hessian is only used in controller/.../ReplicasInfoManager.java. No code changes needed for broker/proxy.

@RockteMQ-AI RockteMQ-AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Targeted fix for the controller startup failure on JDK 17.

The --add-opens java.base/java.lang=ALL-UNNAMED is correctly placed in the JDK 9+ branch of choose_gc_options(), and the comment clearly explains the root cause (Hessian SerializerFactory reflective access to StackTraceElement.classLoaderName via DLedger) and references both #10612 and #10170.

While --add-opens is a workaround rather than a proper fix, it is the pragmatic solution here since the problematic reflective access is in a transitive dependency (DLedger → Hessian). A proper upstream fix would require changes in those libraries.


Automated review by github-manager-bot

@itxaiohanglover

Copy link
Copy Markdown

Thanks for the thorough scope confirmation and the table in the PR body — controller-only makes sense given the grep result showing Hessian is used solely in ReplicasInfoManager.java. No further questions from me.

@fuyou001 fuyou001 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@123123213weqw

Copy link
Copy Markdown
Author

@fuyou001 thanks for the APPROVED!

The PR is MERGEABLE with your approval, but the GitHub Actions workflows are still in action_required (first-time-contributor gate) and haven't run yet — gh pr checks shows "no checks reported". Could you approve the workflow runs on the Actions tab so CI can validate, and then this should be ready to merge?

Happy to make any other changes if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Bug title 使用jdk17启动mqcontroller报错 [Bug] Does not support JDK 17

4 participants