redoom opened a new pull request, #15764:
URL: https://github.com/apache/dubbo/pull/15764
## Problem Description
When compiling Apache Dubbo with JDK 25 and using it as a dependency in
applications running with Netty 4.1.x, the following error occurs at runtime
during class loading:
```
java.lang.NoClassDefFoundError: io/netty/channel/MultiThreadIoEventLoopGroup
at
org.apache.dubbo.remoting.transport.netty4.NettyServer.createBossGroup(NettyServer.java:154)
...
Caused by: java.lang.ClassNotFoundException:
io.netty.channel.MultiThreadIoEventLoopGroup
at
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
```
**Environment:**
- Compile with: JDK 25
- Runtime Netty version: 4.1.x (in dependent applications)
- Dubbo compile-time Netty version: 4.2.2.Final
**Key observation:** The same code compiled with JDK 17 works correctly.
## Root Cause Analysis
### JVM Specification Violation
According to the **JVM Specification SE 25, Section 4.7.4 - The
StackMapTable Attribute** ([[official
documentation](https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.4)](https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.4)):
> "A StackMapTable attribute is used during the process of verification by
type checking."
And more specifically:
> "A stack map frame specifies (either explicitly or implicitly) the
bytecode offset at which it applies, and the **verification types of local
variables and operand stack entries** for that offset."
For object types, the specification defines:
> "The Object_variable_info item indicates that the location has the
verification type which is the **class represented by the CONSTANT_Class_info
structure** found in the constant_pool table at the index given by cpool_index."
```
Object_variable_info {
u1 tag = ITEM_Object; /* 7 */
u2 cpool_index;
}
```
### The Problem in NettyEventLoopFactory
The problematic code in `NettyEventLoopFactory.java` (lines 50-55):
```java
public static EventLoopGroup eventLoopGroup(int threads, String
threadFactoryName) {
ThreadFactory threadFactory = new
DefaultThreadFactory(threadFactoryName, true);
return shouldEpoll()
? new EpollEventLoopGroup(threads, threadFactory)
: new NioEventLoopGroup(threads, threadFactory);
}
```
### JDK 25's Type Inference Behavior
When JDK 25 compiles the ternary operator above, it performs **Least Upper
Bound (LUB)** type inference:
1. Branch 1 returns: `EpollEventLoopGroup`
2. Branch 2 returns: `NioEventLoopGroup`
3. In Netty 4.2.x, both classes extend: `MultiThreadIoEventLoopGroup`
4. JDK 25 infers this common parent type as the LUB
### Bytecode Evidence
**With ternary operator (problematic):**
```
Bytecode analysis using javap -v:
public static io.netty.channel.EventLoopGroup eventLoopGroup(int,
java.lang.String);
Code:
...
25: goto 37 // Jump to merge point
28: new #29 // class
io/netty/channel/nio/NioEventLoopGroup
...
37: areturn // Merge point
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 28
locals = [ class java/util/concurrent/ThreadFactory ]
frame_type = 72 /* same_locals_1_stack_item */
stack = [ class io/netty/channel/MultiThreadIoEventLoopGroup ] ←
PROBLEM!
```
The `frame_type = 72` corresponds to `same_locals_1_stack_item_frame` as
defined in JVM Spec 4.7.4:
> "The frame type same_locals_1_stack_item_frame is represented by tags in
the range [64, 127]. This frame type indicates that the frame has exactly the
same local variables as the previous frame and that **the operand stack has one
entry**."
```
same_locals_1_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */
verification_type_info stack[1]; ← Records the common type!
}
```
JDK 25 writes `MultiThreadIoEventLoopGroup` into the StackMapTable as the
`Object_variable_info` for the operand stack entry at the merge point.
### Runtime Failure Mechanism
When the JVM loads the class:
1. Reads the StackMapTable attribute
2. Encounters `Object_variable_info` pointing to
`io/netty/channel/MultiThreadIoEventLoopGroup`
3. Attempts to load this class for verification
4. **Fails because Netty 4.1.x doesn't have this class** (introduced in
Netty 4.2.0)
5. Throws `NoClassDefFoundError` during class loading verification
This is a **class loading failure**, not a runtime execution failure.
### Why JDK 17 Doesn't Have This Issue
JDK 17 uses a more conservative type inference strategy:
- Prefers to use the declared return type (`EventLoopGroup` interface)
- Does not infer concrete common parent classes for StackMapTable
- Results in less precise but more compatible bytecode
### Why if-else Solves the Problem
**With if-else statement:**
```java
if (shouldEpoll()) {
return new EpollEventLoopGroup(threads, threadFactory);
} else {
return new NioEventLoopGroup(threads, threadFactory);
}
```
**Bytecode:**
```
public static io.netty.channel.EventLoopGroup eventLoopGroup(int,
java.lang.String);
Code:
...
13: ifeq 26
16: new #24 // class
io/netty/channel/epoll/EpollEventLoopGroup
...
25: areturn // Direct return, no merge point
26: new #29 // class
io/netty/channel/nio/NioEventLoopGroup
...
35: areturn // Direct return, no merge point
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 26
locals = [ class java/util/concurrent/ThreadFactory ]
```
Key difference: **No merge point**, using `same_frame` instead:
> "The frame type same_frame is represented by tags in the range [0-63].
This frame type indicates that the frame has exactly the same local variables
as the previous frame and that **the operand stack is empty**."
```
same_frame {
u1 frame_type = SAME; /* 0-63 */
}
```
**No operand stack entries** = No `Object_variable_info` = No class
reference to verify = No `NoClassDefFoundError`.
## Solution
Replace the ternary operator with an if-else statement in
`NettyEventLoopFactory.eventLoopGroup()` method.
**File:**
`dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyEventLoopFactory.java`
**Changes:**
```diff
public static EventLoopGroup eventLoopGroup(int threads, String
threadFactoryName) {
ThreadFactory threadFactory = new
DefaultThreadFactory(threadFactoryName, true);
- return shouldEpoll()
- ? new EpollEventLoopGroup(threads, threadFactory)
- : new NioEventLoopGroup(threads, threadFactory);
+ // Use if-else instead of ternary operator to avoid JDK 25 type
inference issue
+ // JDK 25 compiler infers MultiThreadIoEventLoopGroup as common parent
type in StackMapTable
+ // which causes NoClassDefFoundError when running with Netty 4.1.x
+ if (shouldEpoll()) {
+ return new EpollEventLoopGroup(threads, threadFactory);
+ } else {
+ return new NioEventLoopGroup(threads, threadFactory);
+ }
}
```
## Impact and Risk Assessment
### Impact Scope
**Affected scenarios** (ALL conditions must be met):
1. ✅ Compile with JDK 25
2. ✅ Use ternary operator returning objects
3. ✅ The two branches have a non-interface common parent class
4. ✅ Runtime classpath doesn't have that common parent class
### Risk Level: **Low**
- **Code change**: Minimal (control flow transformation, no logic change)
- **Test coverage**: All existing tests pass
- **Backward compatibility**: Fully maintained (JDK 8-24 unaffected)
- **Forward compatibility**: Enables JDK 25 support
### Benefits
1. ✅ **Fixes JDK 25 compatibility**
2. ✅ **No functional changes**
3. ✅ **No performance impact**
4. ✅ **Backward compatible with all JDK versions**
5. ✅ **Prevents similar issues in the future**
## Related Issues
This fix addresses the compatibility issue introduced by:
- JDK 25's enhanced type inference for ternary operators
- Netty 4.2.0's architectural change (introduced
`MultiThreadIoEventLoopGroup`)
## References
1. **JVM Specification SE 25 - StackMapTable Attribute**
https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.4
2. **Netty 4.2.0 Release Notes**
https://netty.io/news/2025/04/03/4-2-0.html
3. **Netty 4.2 Migration Guide**
https://github.com/netty/netty/wiki/Netty-4.2-Migration-Guide
4. **JDK 25 Release Notes**
https://openjdk.org/projects/jdk/25/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]