When a method is annotated with both @LegacyClientCallable and @AllowInert, the instrumented bridge method generated by ClassInstrumentationUtil is only annotated with @ClientCallable. The @AllowInert annotation is silently dropped, so the method cannot be called from the client side when the component is inert, contrary to the developer's intent.
Steps to reproduce
public class MyComponent extends Component {
@LegacyClientCallable
@AllowInert
public void handleCallback(JsonValue value) { ... }
}
Instrument the class with JsonMigrationHelper. Reflectively inspect the handleCallback method on the instrumented subclass: @AllowInert is absent from the bridge method.
Expected behaviour
The bridge method carries both @ClientCallable and @AllowInert.
Actual behaviour
The bridge method carries only @ClientCallable.
Root cause
generateMethodOverride in ClassInstrumentationUtil (line 432) unconditionally emits exactly one annotation and never iterates the original method's annotations:
mv.visitAnnotation(Type.getDescriptor(ClientCallable.class), true);
The fix is to scan the original method's annotations after emitting @ClientCallable and re-emit any @AllowInert found, using a class-name comparison to avoid a compile-time dependency on Vaadin 24+:
mv.visitAnnotation(Type.getDescriptor(ClientCallable.class), true).visitEnd();
for (Annotation annotation : method.getAnnotations()) {
if ("com.vaadin.flow.component.internal.AllowInert"
.equals(annotation.annotationType().getName())) {
mv.visitAnnotation(Type.getDescriptor(annotation.annotationType()), true).visitEnd();
}
}
When a method is annotated with both
@LegacyClientCallableand@AllowInert, the instrumented bridge method generated byClassInstrumentationUtilis only annotated with@ClientCallable. The@AllowInertannotation is silently dropped, so the method cannot be called from the client side when the component is inert, contrary to the developer's intent.Steps to reproduce
Instrument the class with
JsonMigrationHelper. Reflectively inspect thehandleCallbackmethod on the instrumented subclass:@AllowInertis absent from the bridge method.Expected behaviour
The bridge method carries both
@ClientCallableand@AllowInert.Actual behaviour
The bridge method carries only
@ClientCallable.Root cause
generateMethodOverrideinClassInstrumentationUtil(line 432) unconditionally emits exactly one annotation and never iterates the original method's annotations:The fix is to scan the original method's annotations after emitting
@ClientCallableand re-emit any@AllowInertfound, using a class-name comparison to avoid a compile-time dependency on Vaadin 24+: