fix(generic): support variadic method invocation via generic call (#3284)

* fix(generic): support variadic method invocation via generic call

* fix(generic): support variadic invocation across reflection paths
diff --git a/common/constant/key.go b/common/constant/key.go
index 2be7ffc..aba807c 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -28,40 +28,41 @@
 )
 
 const (
-	GroupKey               = "group"
-	VersionKey             = "version"
-	InterfaceKey           = "interface"
-	MessageSizeKey         = "message_size"
-	PathKey                = "path"
-	ServiceKey             = "service"
-	MethodsKey             = "methods"
-	TimeoutKey             = "timeout"
-	CategoryKey            = "category"
-	CheckKey               = "check"
-	EnabledKey             = "enabled"
-	SideKey                = "side"
-	OverrideProvidersKey   = "providerAddresses"
-	BeanNameKey            = "bean.name"
-	GenericKey             = "generic"
-	ClassifierKey          = "classifier"
-	TokenKey               = "token"
-	LocalAddr              = "local-addr"
-	RemoteAddr             = "remote-addr"
-	DefaultRemotingTimeout = 1000
-	ReleaseKey             = "release"
-	AnyhostKey             = "anyhost"
-	PortKey                = "port"
-	ProtocolKey            = "protocol"
-	PathSeparator          = "/"
-	DotSeparator           = "."
-	CommaSeparator         = ","
-	SslEnabledKey          = "ssl-enabled"
-	ParamsTypeKey          = "parameter-type-names" // key used in pass through invoker factory, to define param type
-	MetadataTypeKey        = "metadata-type"
-	MaxCallSendMsgSize     = "max-call-send-msg-size"
-	MaxServerSendMsgSize   = "max-server-send-msg-size"
-	MaxCallRecvMsgSize     = "max-call-recv-msg-size"
-	MaxServerRecvMsgSize   = "max-server-recv-msg-size"
+	GroupKey                    = "group"
+	VersionKey                  = "version"
+	InterfaceKey                = "interface"
+	MessageSizeKey              = "message_size"
+	PathKey                     = "path"
+	ServiceKey                  = "service"
+	MethodsKey                  = "methods"
+	TimeoutKey                  = "timeout"
+	CategoryKey                 = "category"
+	CheckKey                    = "check"
+	EnabledKey                  = "enabled"
+	SideKey                     = "side"
+	OverrideProvidersKey        = "providerAddresses"
+	BeanNameKey                 = "bean.name"
+	GenericKey                  = "generic"
+	GenericVariadicCallSliceKey = "generic-variadic-call-slice" // internal marker from generic filter to variadic reflection dispatchers
+	ClassifierKey               = "classifier"
+	TokenKey                    = "token"
+	LocalAddr                   = "local-addr"
+	RemoteAddr                  = "remote-addr"
+	DefaultRemotingTimeout      = 1000
+	ReleaseKey                  = "release"
+	AnyhostKey                  = "anyhost"
+	PortKey                     = "port"
+	ProtocolKey                 = "protocol"
+	PathSeparator               = "/"
+	DotSeparator                = "."
+	CommaSeparator              = ","
+	SslEnabledKey               = "ssl-enabled"
+	ParamsTypeKey               = "parameter-type-names" // key used in pass through invoker factory, to define param type
+	MetadataTypeKey             = "metadata-type"
+	MaxCallSendMsgSize          = "max-call-send-msg-size"
+	MaxServerSendMsgSize        = "max-server-send-msg-size"
+	MaxCallRecvMsgSize          = "max-call-recv-msg-size"
+	MaxServerRecvMsgSize        = "max-server-recv-msg-size"
 
 	// TODO: remove KeepAliveInterval and KeepAliveInterval in version 4.0.0
 	KeepAliveInterval = "keep-alive-interval"
diff --git a/common/rpc_service.go b/common/rpc_service.go
index 84da0cd..cc936d3 100644
--- a/common/rpc_service.go
+++ b/common/rpc_service.go
@@ -132,6 +132,11 @@
 	return m.replyType
 }
 
+// IsVariadic returns true if the method has a variadic (...T) final parameter.
+func (m *MethodType) IsVariadic() bool {
+	return m.method.Type.IsVariadic()
+}
+
 // SuiteContext transfers @ctx to reflect.Value type or get it from @m.ctxType.
 func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value {
 	if ctxV := reflect.ValueOf(ctx); ctxV.IsValid() {
diff --git a/filter/generic/service_filter.go b/filter/generic/service_filter.go
index 16925f7..0a986a9 100644
--- a/filter/generic/service_filter.go
+++ b/filter/generic/service_filter.go
@@ -19,6 +19,8 @@
 
 import (
 	"context"
+	"reflect"
+	"strings"
 	"sync"
 )
 
@@ -35,7 +37,9 @@
 	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/common/extension"
 	"dubbo.apache.org/dubbo-go/v3/filter"
+	"dubbo.apache.org/dubbo-go/v3/filter/generic/generalizer"
 	"dubbo.apache.org/dubbo-go/v3/protocol/base"
+	dubboHessian "dubbo.apache.org/dubbo-go/v3/protocol/dubbo/hessian2"
 	"dubbo.apache.org/dubbo-go/v3/protocol/invocation"
 	"dubbo.apache.org/dubbo-go/v3/protocol/result"
 )
@@ -78,44 +82,339 @@
 	`, mtdName, types, args)
 
 	// get the type of the argument
-	ivkUrl := invoker.GetURL()
-	svc := common.ServiceMap.GetServiceByServiceKey(ivkUrl.Protocol, ivkUrl.ServiceKey())
+	ivkURL := invoker.GetURL()
+	svc := common.ServiceMap.GetServiceByServiceKey(ivkURL.Protocol, ivkURL.ServiceKey())
 	method := svc.Method()[mtdName]
 	if method == nil {
 		return &result.RPCResult{
-			Err: perrors.Errorf("\"%s\" method is not found, service key: %s", mtdName, ivkUrl.ServiceKey()),
+			Err: perrors.Errorf("\"%s\" method is not found, service key: %s", mtdName, ivkURL.ServiceKey()),
 		}
 	}
+
 	argsType := method.ArgsType()
+	if err := validateGenericArgs(method.IsVariadic(), len(argsType), len(args), mtdName); err != nil {
+		return &result.RPCResult{Err: err}
+	}
 
 	// get generic info from attachments of invocation, the default value is "true"
 	generic := inv.GetAttachmentWithDefaultValue(constant.GenericKey, constant.GenericSerializationDefault)
 	// get generalizer according to value in the `generic`
-	g := getGeneralizer(generic)
-
-	if len(args) != len(argsType) {
-		return &result.RPCResult{
-			Err: perrors.Errorf("the number of args(=%d) is not matched with \"%s\" method", len(args), mtdName),
-		}
+	// realize
+	newArgs, err := realizeInvocationArgs(getGeneralizer(generic), argsType, args, method.IsVariadic(), types)
+	if err != nil {
+		return &result.RPCResult{Err: err}
 	}
 
-	// realize
+	newIvc := invocation.NewRPCInvocation(mtdName, newArgs, inv.Attachments())
+	newIvc.SetReply(inv.Reply())
+	if method.IsVariadic() {
+		newIvc.SetAttribute(constant.GenericVariadicCallSliceKey, true)
+	}
+
+	return invoker.Invoke(ctx, newIvc)
+}
+
+// validateGenericArgs checks the generic arg count against the method signature.
+// Variadic methods accept any count >= the fixed parameter count.
+func validateGenericArgs(isVariadic bool, argsTypeCount, argCount int, methodName string) error {
+	if isVariadic {
+		if argCount >= argsTypeCount-1 {
+			return nil
+		}
+	} else if argCount == argsTypeCount {
+		return nil
+	}
+
+	return perrors.Errorf("the number of args(=%d) is not matched with \"%s\" method", argCount, methodName)
+}
+
+// realizeInvocationArgs realizes generic args and packs a variadic tail into the declared slice type.
+func realizeInvocationArgs(g generalizer.Generalizer, argsType []reflect.Type, args []hessian.Object, isVariadic bool, types any) ([]any, error) {
+	if !isVariadic {
+		return realizeFixedArgs(g, args, argsType)
+	}
+
+	newArgs, err := realizeFixedArgs(g, args[:len(argsType)-1], argsType[:len(argsType)-1])
+	if err != nil {
+		return nil, err
+	}
+
+	variadicArg, err := realizeVariadicArg(g, args[len(argsType)-1:], argsType[len(argsType)-1], variadicTypeName(types))
+	if err != nil {
+		return nil, err
+	}
+
+	return append(newArgs, variadicArg), nil
+}
+
+// realizeFixedArgs realizes non-variadic parameters one by one.
+func realizeFixedArgs(g generalizer.Generalizer, args []hessian.Object, argsType []reflect.Type) ([]any, error) {
 	newArgs := make([]any, len(argsType))
-	for i := 0; i < len(argsType); i++ {
+	for i := range argsType {
 		newArg, err := g.Realize(args[i], argsType[i])
 		if err != nil {
-			return &result.RPCResult{
-				Err: perrors.Errorf("realization failed, %v", err),
-			}
+			return nil, perrors.Errorf("realization of arg[%d] failed: %v", i, err)
 		}
 		newArgs[i] = newArg
 	}
 
-	// build a normal invocation
-	newIvc := invocation.NewRPCInvocation(mtdName, newArgs, inv.Attachments())
-	newIvc.SetReply(inv.Reply())
+	return newArgs, nil
+}
 
-	return invoker.Invoke(ctx, newIvc)
+// realizeVariadicArg realizes the variadic tail into the declared slice type.
+// It unwraps a single arg only when its declared generic type matches the variadic slice.
+func realizeVariadicArg(g generalizer.Generalizer, args []hessian.Object, variadicSliceType reflect.Type, variadicType string) (any, error) {
+	variadicArgs := normalizeVariadicArgs(args, variadicSliceType, variadicType)
+	slice := reflect.MakeSlice(variadicSliceType, len(variadicArgs), len(variadicArgs))
+	elemType := variadicSliceType.Elem()
+
+	for i, arg := range variadicArgs {
+		realized, err := g.Realize(arg, elemType)
+		if err != nil {
+			return nil, perrors.Errorf("realization of variadic arg[%d] failed: %v", i, err)
+		}
+		realizedValue, err := assignableValue(realized, elemType)
+		if err != nil {
+			return nil, perrors.Errorf("realization of variadic arg[%d] failed: %v", i, err)
+		}
+		slice.Index(i).Set(realizedValue)
+	}
+
+	return slice.Interface(), nil
+}
+
+// assignableValue fits a realized value into the target type without panicking on Set.
+func assignableValue(value any, targetType reflect.Type) (reflect.Value, error) {
+	if value == nil {
+		if canBeNil(targetType) {
+			return reflect.Zero(targetType), nil
+		}
+		return reflect.Value{}, perrors.Errorf("nil is not assignable to %s", targetType)
+	}
+
+	realizedValue := reflect.ValueOf(value)
+	if realizedValue.Type().AssignableTo(targetType) {
+		return realizedValue, nil
+	}
+	if realizedValue.Type().ConvertibleTo(targetType) {
+		return realizedValue.Convert(targetType), nil
+	}
+
+	return reflect.Value{}, perrors.Errorf("type %s is not assignable to %s", realizedValue.Type(), targetType)
+}
+
+func canBeNil(typ reflect.Type) bool {
+	switch typ.Kind() {
+	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+		return true
+	default:
+		return false
+	}
+}
+
+// variadicTypeName returns the last generic type name when the caller provides $invoke type metadata.
+func variadicTypeName(types any) string {
+	switch typeNames := types.(type) {
+	case []string:
+		if len(typeNames) == 0 {
+			return ""
+		}
+		return typeNames[len(typeNames)-1]
+	case []any:
+		if len(typeNames) == 0 {
+			return ""
+		}
+		if typeName, ok := typeNames[len(typeNames)-1].(string); ok {
+			return typeName
+		}
+	}
+
+	return ""
+}
+
+// normalizeVariadicArgs unwraps one packed array only when the generic type says it
+// is the variadic slice itself; otherwise the single arg stays as one variadic value.
+func normalizeVariadicArgs(args []hessian.Object, variadicSliceType reflect.Type, variadicType string) []hessian.Object {
+	if len(args) != 1 {
+		return args
+	}
+	if !shouldUnwrapPackedVariadicArg(variadicType, variadicSliceType) {
+		if variadicType != "" {
+			return args
+		}
+		return normalizeVariadicArgsWithoutType(args[0], variadicSliceType)
+	}
+	if args[0] == nil {
+		return nil
+	}
+
+	return unwrapToSlice(args[0])
+}
+
+// normalizeVariadicArgsWithoutType keeps dubbo-go compatibility when generic callers omit `types`.
+// A real single variadic value stays packed as one element, while a packed tail slice is unwrapped once.
+func normalizeVariadicArgsWithoutType(arg hessian.Object, variadicSliceType reflect.Type) []hessian.Object {
+	if arg == nil {
+		return nil
+	}
+
+	v := reflect.ValueOf(arg)
+	if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
+		return []hessian.Object{arg}
+	}
+
+	elemType := variadicSliceType.Elem()
+	argType := v.Type()
+	if elemType.Kind() != reflect.Interface && (argType.AssignableTo(elemType) || argType.ConvertibleTo(elemType)) {
+		return []hessian.Object{arg}
+	}
+
+	return unwrapToSlice(arg)
+}
+
+// shouldUnwrapPackedVariadicArg matches the declared variadic slice against the
+// generic tail type, including Java names and JVM array descriptors.
+func shouldUnwrapPackedVariadicArg(variadicType string, variadicSliceType reflect.Type) bool {
+	if variadicType == "" {
+		return false
+	}
+
+	for _, typeName := range javaTypeNamesForType(variadicSliceType) {
+		if variadicType == typeName {
+			return true
+		}
+	}
+
+	elemType := variadicSliceType.Elem()
+	if elemType.Kind() == reflect.Interface && (variadicType == "[Ljava.lang.Object;" || variadicType == "java.lang.Object[]") {
+		return true
+	}
+
+	return false
+}
+
+// javaTypeNamesForType returns the generic type spellings we accept for the variadic slice.
+func javaTypeNamesForType(typ reflect.Type) []string {
+	zero := reflect.Zero(typ)
+	if !zero.IsValid() {
+		return nil
+	}
+
+	names := make([]string, 0, 2)
+	if name, err := dubboHessian.GetJavaName(zero.Interface()); err == nil && name != "" {
+		names = append(names, name)
+	}
+	if desc := dubboHessian.GetClassDesc(zero.Interface()); desc != "" && desc != "V" {
+		names = appendUniqueString(names, desc)
+	}
+	if desc := jvmArrayDescriptorForType(typ); desc != "" {
+		names = appendUniqueString(names, desc)
+	}
+
+	return names
+}
+
+// jvmArrayDescriptorForType builds descriptors like [B, [[B or [[Ljava.lang.String;.
+func jvmArrayDescriptorForType(typ reflect.Type) string {
+	if typ.Kind() != reflect.Slice && typ.Kind() != reflect.Array {
+		return ""
+	}
+
+	depth := 0
+	for typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array {
+		depth++
+		typ = typ.Elem()
+	}
+
+	leaf := jvmLeafDescriptorForType(typ)
+	if leaf == "" {
+		return ""
+	}
+
+	return strings.Repeat("[", depth) + leaf
+}
+
+func jvmLeafDescriptorForType(typ reflect.Type) string {
+	switch typ.Kind() {
+	case reflect.Bool:
+		return "Z"
+	case reflect.Int8, reflect.Uint8:
+		return "B"
+	case reflect.Int16:
+		return "S"
+	case reflect.Uint16:
+		return "C"
+	case reflect.Int, reflect.Int64:
+		return "J"
+	case reflect.Int32:
+		return "I"
+	case reflect.Float32:
+		return "F"
+	case reflect.Float64:
+		return "D"
+	case reflect.String:
+		return "Ljava.lang.String;"
+	case reflect.Interface:
+		return "Ljava.lang.Object;"
+	case reflect.Map:
+		return "Ljava.util.Map;"
+	case reflect.Struct:
+		if typ.PkgPath() == "time" && typ.Name() == "Time" {
+			return "Ljava.util.Date;"
+		}
+		return "Ljava.lang.Object;"
+	default:
+		zero := reflect.New(typ).Elem().Interface()
+		desc := dubboHessian.GetClassDesc(zero)
+		switch desc {
+		case "", "V", "java.util.List":
+			return ""
+		case "java.lang.String":
+			return "Ljava.lang.String;"
+		case "java.lang.Object":
+			return "Ljava.lang.Object;"
+		case "java.util.Date":
+			return "Ljava.util.Date;"
+		case "java.util.Map":
+			return "Ljava.util.Map;"
+		}
+		if len(desc) == 1 {
+			return desc
+		}
+		if strings.HasPrefix(desc, "L") && strings.HasSuffix(desc, ";") {
+			return desc
+		}
+		if strings.Contains(desc, ".") {
+			return "L" + strings.ReplaceAll(desc, ".", "/") + ";"
+		}
+		return ""
+	}
+}
+
+func appendUniqueString(values []string, value string) []string {
+	for _, existing := range values {
+		if existing == value {
+			return values
+		}
+	}
+	return append(values, value)
+}
+
+// unwrapToSlice returns slice/array elements, or keeps obj as one variadic element.
+func unwrapToSlice(obj hessian.Object) []hessian.Object {
+	if obj == nil {
+		return []hessian.Object{nil}
+	}
+	v := reflect.ValueOf(obj)
+	if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
+		out := make([]hessian.Object, v.Len())
+		for i := 0; i < v.Len(); i++ {
+			out[i] = v.Index(i).Interface()
+		}
+		return out
+	}
+	// not a collection — treat as single variadic element
+	return []hessian.Object{obj}
 }
 
 func (f *genericServiceFilter) OnResponse(_ context.Context, result result.Result, _ base.Invoker, inv base.Invocation) result.Result {
diff --git a/filter/generic/service_filter_test.go b/filter/generic/service_filter_test.go
index b28d25e..84756f4 100644
--- a/filter/generic/service_filter_test.go
+++ b/filter/generic/service_filter_test.go
@@ -71,6 +71,22 @@
 	return nil, perrors.Errorf("people not found")
 }
 
+func (s *MockHelloService) HelloVariadic(prefix string, names ...string) (string, error) {
+	return prefix, nil
+}
+
+func (s *MockHelloService) EchoVariadic(args ...any) ([]any, error) {
+	return args, nil
+}
+
+func (s *MockHelloService) BytesVariadic(args ...[]byte) ([][]byte, error) {
+	return args, nil
+}
+
+func (s *MockHelloService) NestedStringVariadic(args ...[]string) ([][]string, error) {
+	return args, nil
+}
+
 func TestServiceFilter_Invoke(t *testing.T) {
 	filter := &genericServiceFilter{}
 
@@ -214,3 +230,252 @@
 	response := filter.OnResponse(context.Background(), rpcResult, nil, invocation1)
 	assert.Equal(t, "result", response.Result())
 }
+
+func TestServiceFilter_InvokeVariadic(t *testing.T) {
+	filter := &genericServiceFilter{}
+
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockInvoker := mock.NewMockInvoker(ctrl)
+	service := &MockHelloService{}
+	ivkURL := common.NewURLWithOptions(
+		common.WithProtocol("test-variadic"),
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.InterfaceKey, service.Reference()),
+		common.WithParamsValue(constant.GenericKey, constant.GenericSerializationDefault),
+	)
+	_, err := common.ServiceMap.Register(ivkURL.GetParam(constant.InterfaceKey, ""),
+		ivkURL.Protocol,
+		"",
+		"",
+		service)
+	require.NoError(t, err)
+	t.Cleanup(func() {
+		_ = common.ServiceMap.UnRegister(ivkURL.GetParam(constant.InterfaceKey, ""), ivkURL.Protocol, ivkURL.ServiceKey())
+	})
+
+	mockInvoker.EXPECT().GetURL().Return(ivkURL).AnyTimes()
+
+	cases := []struct {
+		name             string
+		inv              *invocation.RPCInvocation
+		assertInvocation func(t *testing.T, inv base.Invocation)
+	}{
+		{
+			name: "fixed plus discrete variadic args",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"HelloVariadic",
+				[]string{"java.lang.String", "java.lang.String", "java.lang.String"},
+				[]hessian.Object{"hello", "alice", "bob"},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "HelloVariadic", inv.MethodName())
+				assert.Equal(t, []any{"hello", []string{"alice", "bob"}}, inv.Arguments())
+			},
+		},
+		{
+			name: "fixed plus single scalar variadic arg",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"HelloVariadic",
+				[]string{"java.lang.String", "java.lang.String"},
+				[]hessian.Object{"hello", "alice"},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "HelloVariadic", inv.MethodName())
+				assert.Equal(t, []any{"hello", []string{"alice"}}, inv.Arguments())
+			},
+		},
+		{
+			name: "fixed plus packed variadic array",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"HelloVariadic",
+				[]string{"java.lang.String", "[Ljava.lang.String;"},
+				[]hessian.Object{"hello", []any{"alice", "bob"}},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "HelloVariadic", inv.MethodName())
+				assert.Equal(t, []any{"hello", []string{"alice", "bob"}}, inv.Arguments())
+			},
+		},
+		{
+			name: "fixed plus packed variadic array without types",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"HelloVariadic",
+				nil,
+				[]hessian.Object{"hello", []string{"alice", "bob"}},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "HelloVariadic", inv.MethodName())
+				assert.Equal(t, []any{"hello", []string{"alice", "bob"}}, inv.Arguments())
+			},
+		},
+		{
+			name: "fixed plus packed nil variadic array",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"HelloVariadic",
+				[]string{"java.lang.String", "[Ljava.lang.String;"},
+				[]hessian.Object{"hello", nil},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "HelloVariadic", inv.MethodName())
+				assert.Equal(t, []any{"hello", []string{}}, inv.Arguments())
+			},
+		},
+		{
+			name: "zero variadic args",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"HelloVariadic",
+				[]string{"java.lang.String"},
+				[]hessian.Object{"hello"},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "HelloVariadic", inv.MethodName())
+				assert.Equal(t, []any{"hello", []string{}}, inv.Arguments())
+			},
+		},
+		{
+			name: "interface variadic keeps nil element",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"EchoVariadic",
+				[]string{"java.lang.Object", "java.lang.Object"},
+				[]hessian.Object{nil, "tail"},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "EchoVariadic", inv.MethodName())
+				assert.Equal(t, []any{[]any{nil, "tail"}}, inv.Arguments())
+			},
+		},
+		{
+			name: "interface variadic keeps a single nil element",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"EchoVariadic",
+				[]string{"java.lang.Object"},
+				[]hessian.Object{nil},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "EchoVariadic", inv.MethodName())
+				assert.Equal(t, []any{[]any{nil}}, inv.Arguments())
+			},
+		},
+		{
+			name: "slice variadic keeps a single slice element",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"BytesVariadic",
+				[]string{"[B"},
+				[]hessian.Object{[]byte("tail")},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "BytesVariadic", inv.MethodName())
+				assert.Equal(t, []any{[][]byte{[]byte("tail")}}, inv.Arguments())
+			},
+		},
+		{
+			name: "slice variadic unwraps packed values without types",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"BytesVariadic",
+				nil,
+				[]hessian.Object{[][]byte{[]byte("a"), []byte("b")}},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "BytesVariadic", inv.MethodName())
+				assert.Equal(t, []any{[][]byte{[]byte("a"), []byte("b")}}, inv.Arguments())
+			},
+		},
+		{
+			name: "slice variadic unwraps nested byte descriptor",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"BytesVariadic",
+				[]string{"[[B"},
+				[]hessian.Object{[][]byte{[]byte("a"), []byte("b")}},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "BytesVariadic", inv.MethodName())
+				assert.Equal(t, []any{[][]byte{[]byte("a"), []byte("b")}}, inv.Arguments())
+			},
+		},
+		{
+			name: "nested string variadic unwraps nested string descriptor",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"NestedStringVariadic",
+				[]string{"[[Ljava.lang.String;"},
+				[]hessian.Object{[][]string{{"a", "b"}, {"c"}}},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "NestedStringVariadic", inv.MethodName())
+				assert.Equal(t, []any{[][]string{{"a", "b"}, {"c"}}}, inv.Arguments())
+			},
+		},
+		{
+			name: "interface variadic unwraps a packed object array",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"EchoVariadic",
+				[]string{"[Ljava.lang.Object;"},
+				[]hessian.Object{[]any{"alice", "bob"}},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "EchoVariadic", inv.MethodName())
+				assert.Equal(t, []any{[]any{"alice", "bob"}}, inv.Arguments())
+			},
+		},
+		{
+			name: "interface variadic keeps packed nil object array empty",
+			inv: invocation.NewRPCInvocation(constant.Generic, []any{
+				"EchoVariadic",
+				[]string{"[Ljava.lang.Object;"},
+				[]hessian.Object{nil},
+			}, map[string]any{
+				constant.GenericKey: constant.GenericSerializationDefault,
+			}),
+			assertInvocation: func(t *testing.T, inv base.Invocation) {
+				assert.Equal(t, "EchoVariadic", inv.MethodName())
+				assert.Equal(t, []any{[]any{}}, inv.Arguments())
+			},
+		},
+	}
+
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			mockInvoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).DoAndReturn(
+				func(_ context.Context, inv base.Invocation) result.Result {
+					marked, ok := inv.GetAttribute(constant.GenericVariadicCallSliceKey)
+					require.True(t, ok)
+					useCallSlice, ok := marked.(bool)
+					require.True(t, ok)
+					assert.True(t, useCallSlice)
+					tt.assertInvocation(t, inv)
+					return &result.RPCResult{}
+				},
+			).Times(1)
+
+			invokeResult := filter.Invoke(context.Background(), mockInvoker, tt.inv)
+			require.NoError(t, invokeResult.Error())
+		})
+	}
+}
diff --git a/protocol/triple/server.go b/protocol/triple/server.go
index 0b31f32..90e72bd 100644
--- a/protocol/triple/server.go
+++ b/protocol/triple/server.go
@@ -644,32 +644,7 @@
 			return params
 		},
 		MethodFunc: func(ctx context.Context, args []any, handler any) (any, error) {
-			in := []reflect.Value{reflect.ValueOf(handler)}
-			in = append(in, reflect.ValueOf(ctx))
-			for _, arg := range args {
-				in = append(in, reflect.ValueOf(arg))
-			}
-			returnValues := method.Func.Call(in)
-			if len(returnValues) == 1 {
-				if isReflectValueNil(returnValues[0]) {
-					return nil, nil
-				}
-				if err, ok := returnValues[0].Interface().(error); ok {
-					return nil, err
-				}
-				return nil, nil
-			}
-			var result any
-			var err error
-			if !isReflectValueNil(returnValues[0]) {
-				result = returnValues[0].Interface()
-			}
-			if len(returnValues) > 1 && !isReflectValueNil(returnValues[1]) {
-				if e, ok := returnValues[1].Interface().(error); ok {
-					err = e
-				}
-			}
-			return result, err
+			return callMethodByReflection(ctx, method, handler, args)
 		},
 	}
 }
@@ -700,6 +675,64 @@
 	}
 }
 
+func callMethodByReflection(ctx context.Context, method reflect.Method, handler any, args []any) (any, error) {
+	in := []reflect.Value{reflect.ValueOf(handler)}
+	in = append(in, reflect.ValueOf(ctx))
+	for _, arg := range args {
+		in = append(in, reflect.ValueOf(arg))
+	}
+
+	var returnValues []reflect.Value
+	if shouldUseGenericVariadicCallSlice(ctx, method, args) {
+		returnValues = method.Func.CallSlice(in)
+	} else {
+		returnValues = method.Func.Call(in)
+	}
+
+	if len(returnValues) == 1 {
+		if isReflectValueNil(returnValues[0]) {
+			return nil, nil
+		}
+		if err, ok := returnValues[0].Interface().(error); ok {
+			return nil, err
+		}
+		return nil, nil
+	}
+	var result any
+	var err error
+	if !isReflectValueNil(returnValues[0]) {
+		result = returnValues[0].Interface()
+	}
+	if len(returnValues) > 1 && !isReflectValueNil(returnValues[1]) {
+		if e, ok := returnValues[1].Interface().(error); ok {
+			err = e
+		}
+	}
+	return result, err
+}
+
+// shouldUseGenericVariadicCallSlice mirrors the ServiceInfo reflection gate for
+// Triple's reflection-based method dispatch.
+func shouldUseGenericVariadicCallSlice(ctx context.Context, method reflect.Method, args []any) bool {
+	if !method.Type.IsVariadic() || len(args) == 0 || len(args) != method.Type.NumIn()-2 {
+		return false
+	}
+
+	value, ok := ctx.Value(constant.DubboCtxKey(constant.GenericVariadicCallSliceKey)).(bool)
+	if !ok || !value {
+		return false
+	}
+
+	lastArg := args[len(args)-1]
+	if lastArg == nil {
+		return false
+	}
+
+	lastArgType := reflect.TypeOf(lastArg)
+	variadicSliceType := method.Type.In(method.Type.NumIn() - 1)
+	return lastArgType.AssignableTo(variadicSliceType) || lastArgType.ConvertibleTo(variadicSliceType)
+}
+
 // generateAttachments transfer http.Header to map[string]any and make all keys lowercase
 func generateAttachments(header http.Header) map[string]any {
 	attachments := make(map[string]any, len(header))
diff --git a/protocol/triple/server_test.go b/protocol/triple/server_test.go
index b42e19d..508b211 100644
--- a/protocol/triple/server_test.go
+++ b/protocol/triple/server_test.go
@@ -922,6 +922,12 @@
 	return &Server{triServer: tri.NewServer("127.0.0.1:0", nil)}
 }
 
+type tripleVariadicReflectionServiceForTest struct{}
+
+func (s *tripleVariadicReflectionServiceForTest) HelloVariadic(ctx context.Context, prefix string, names ...string) (string, error) {
+	return prefix + ":" + fmt.Sprint(len(names)), nil
+}
+
 func TestExtractUnaryInvocationArgs(t *testing.T) {
 	t.Run("from non-idl argument slice", func(t *testing.T) {
 		name := "alice"
@@ -937,6 +943,28 @@
 	})
 }
 
+func TestBuildMethodInfoWithReflectionVariadic(t *testing.T) {
+	svc := &tripleVariadicReflectionServiceForTest{}
+	method, ok := reflect.TypeOf(svc).MethodByName("HelloVariadic")
+	require.True(t, ok)
+
+	methodInfo := buildMethodInfoWithReflection(method)
+	require.NotNil(t, methodInfo)
+
+	t.Run("generic packed variadic tail uses call slice", func(t *testing.T) {
+		ctx := context.WithValue(context.Background(), constant.DubboCtxKey(constant.GenericVariadicCallSliceKey), true)
+		res, err := methodInfo.MethodFunc(ctx, []any{"hello", []string{"alice", "bob"}}, svc)
+		require.NoError(t, err)
+		assert.Equal(t, "hello:2", res)
+	})
+
+	t.Run("ordinary discrete variadic call remains unchanged", func(t *testing.T) {
+		res, err := methodInfo.MethodFunc(context.Background(), []any{"hello", "alice", "bob"}, svc)
+		require.NoError(t, err)
+		assert.Equal(t, "hello:2", res)
+	})
+}
+
 func TestWrapTripleResponse(t *testing.T) {
 	resp := tri.NewResponse("already-wrapped")
 	assert.Same(t, resp, wrapTripleResponse(resp))
diff --git a/proxy/proxy_factory/default.go b/proxy/proxy_factory/default.go
index b74e5be..cc52569 100644
--- a/proxy/proxy_factory/default.go
+++ b/proxy/proxy_factory/default.go
@@ -130,7 +130,8 @@
 	}
 
 	// prepare argv
-	if (len(method.ArgsType()) == 1 || len(method.ArgsType()) == 2 && method.ReplyType() == nil) && method.ArgsType()[0].String() == "[]interface {}" {
+	useCallSlice := shouldUseGenericVariadicCallSlice(invocation, method, args)
+	if !useCallSlice && (len(method.ArgsType()) == 1 || len(method.ArgsType()) == 2 && method.ReplyType() == nil) && method.ArgsType()[0].String() == "[]interface {}" {
 		in = append(in, reflect.ValueOf(args))
 	} else {
 		for i := 0; i < len(args); i++ {
@@ -150,7 +151,7 @@
 	var replyv reflect.Value
 	var retErr any
 
-	returnValues, callErr := callLocalMethod(method.Method(), in)
+	returnValues, callErr := callLocalMethod(method.Method(), in, useCallSlice)
 
 	if callErr != nil {
 		logger.Errorf("Invoke function error: %+v, service: %#v", callErr, url)
@@ -176,6 +177,33 @@
 	return result
 }
 
+// shouldUseGenericVariadicCallSlice only enables CallSlice for the generic variadic path
+// after the filter has already packed the variadic tail into the declared slice type.
+func shouldUseGenericVariadicCallSlice(invocation base.Invocation, method *common.MethodType, args []any) bool {
+	if !method.IsVariadic() || len(args) != len(method.ArgsType()) || len(args) == 0 {
+		return false
+	}
+
+	value, ok := invocation.GetAttribute(constant.GenericVariadicCallSliceKey)
+	if !ok {
+		return false
+	}
+
+	useCallSlice, ok := value.(bool)
+	if !ok || !useCallSlice {
+		return false
+	}
+
+	lastArg := args[len(args)-1]
+	if lastArg == nil {
+		return true
+	}
+
+	lastArgType := reflect.TypeOf(lastArg)
+	variadicSliceType := method.ArgsType()[len(method.ArgsType())-1]
+	return lastArgType.AssignableTo(variadicSliceType) || lastArgType.ConvertibleTo(variadicSliceType)
+}
+
 func getProviderURL(url *common.URL) *common.URL {
 	if url.SubURL == nil {
 		return url
@@ -203,6 +231,13 @@
 	args := invocation.Arguments()
 	result := new(result.RPCResult)
 	if method, ok := tpi.methodMap[name]; ok {
+		// ServiceInfo reflection paths only receive ctx/args, so carry the generic
+		// variadic marker through ctx for the downstream CallSlice check.
+		if marked, ok := invocation.GetAttribute(constant.GenericVariadicCallSliceKey); ok {
+			if useCallSlice, ok := marked.(bool); ok && useCallSlice {
+				ctx = context.WithValue(ctx, constant.DubboCtxKey(constant.GenericVariadicCallSliceKey), true)
+			}
+		}
 		res, err := method.MethodFunc(ctx, args, tpi.svc)
 		result.SetResult(res)
 		if err != nil {
diff --git a/proxy/proxy_factory/default_test.go b/proxy/proxy_factory/default_test.go
index 28f48ef..6c7c300 100644
--- a/proxy/proxy_factory/default_test.go
+++ b/proxy/proxy_factory/default_test.go
@@ -18,17 +18,21 @@
 package proxy_factory
 
 import (
+	"context"
 	"fmt"
 	"testing"
 )
 
 import (
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 import (
 	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/protocol/base"
+	"dubbo.apache.org/dubbo-go/v3/protocol/invocation"
 )
 
 func TestGetProxy(t *testing.T) {
@@ -58,3 +62,31 @@
 	invoker := proxyFactory.GetInvoker(url)
 	assert.True(t, invoker.IsAvailable())
 }
+
+func TestInfoProxyInvoker_InvokePropagatesGenericVariadicMarker(t *testing.T) {
+	info := &common.ServiceInfo{
+		Methods: []common.MethodInfo{
+			{
+				Name: "HelloVariadic",
+				MethodFunc: func(ctx context.Context, args []any, handler any) (any, error) {
+					marked, ok := ctx.Value(constant.DubboCtxKey(constant.GenericVariadicCallSliceKey)).(bool)
+					require.True(t, ok)
+					assert.True(t, marked)
+					assert.Equal(t, []any{"hello", []string{"alice", "bob"}}, args)
+					return "ok", nil
+				},
+			},
+		},
+	}
+
+	invoker := newInfoInvoker(common.NewURLWithOptions(), info, struct{}{})
+	inv := invocation.NewRPCInvocationWithOptions(
+		invocation.WithMethodName("HelloVariadic"),
+		invocation.WithArguments([]any{"hello", []string{"alice", "bob"}}),
+	)
+	inv.SetAttribute(constant.GenericVariadicCallSliceKey, true)
+
+	res := invoker.Invoke(context.Background(), inv)
+	require.NoError(t, res.Error())
+	assert.Equal(t, "ok", res.Result())
+}
diff --git a/proxy/proxy_factory/invoker_test.go b/proxy/proxy_factory/invoker_test.go
index 5757f6e..e71791c 100644
--- a/proxy/proxy_factory/invoker_test.go
+++ b/proxy/proxy_factory/invoker_test.go
@@ -43,6 +43,14 @@
 	return "hello:" + name, nil
 }
 
+func (s *ProxyInvokerService) EchoVariadic(args ...any) ([]any, error) {
+	return args, nil
+}
+
+func (s *ProxyInvokerService) CountByteSlices(args ...[]byte) (int, error) {
+	return len(args), nil
+}
+
 type PassThroughService struct{}
 
 func (s *PassThroughService) Service(method string, argTypes []string, args [][]byte, attachments map[string]any) (any, error) {
@@ -148,3 +156,36 @@
 		assert.EqualError(t, result.Error(), "the param type is not []byte")
 	})
 }
+
+func TestProxyInvoker_InvokeVariadicCallSliceGating(t *testing.T) {
+	const (
+		protocol      = "test-variadic-protocol"
+		interfaceName = "ProxyInvokerVariadicService"
+	)
+	registerService(t, protocol, interfaceName, &ProxyInvokerService{})
+	u := newURL(protocol, interfaceName)
+	invoker := &ProxyInvoker{BaseInvoker: *base.NewBaseInvoker(u)}
+
+	t.Run("generic variadic marker expands packed slice", func(t *testing.T) {
+		inv := invocation.NewRPCInvocationWithOptions(
+			invocation.WithMethodName("EchoVariadic"),
+			invocation.WithArguments([]any{[]any{"alice", "bob"}}),
+		)
+		inv.SetAttribute(constant.GenericVariadicCallSliceKey, true)
+
+		res := invoker.Invoke(context.Background(), inv)
+		require.NoError(t, res.Error())
+		assert.Equal(t, []any{"alice", "bob"}, res.Result())
+	})
+
+	t.Run("ordinary slice variadic element does not trigger call slice", func(t *testing.T) {
+		inv := invocation.NewRPCInvocationWithOptions(
+			invocation.WithMethodName("CountByteSlices"),
+			invocation.WithArguments([]any{[]byte("alice")}),
+		)
+
+		res := invoker.Invoke(context.Background(), inv)
+		require.NoError(t, res.Error())
+		assert.Equal(t, 1, res.Result())
+	})
+}
diff --git a/proxy/proxy_factory/pass_through.go b/proxy/proxy_factory/pass_through.go
index 21083ac..edc24d0 100644
--- a/proxy/proxy_factory/pass_through.go
+++ b/proxy/proxy_factory/pass_through.go
@@ -112,7 +112,7 @@
 	var replyv reflect.Value
 	var retErr any
 
-	returnValues, callErr := callLocalMethod(method.Method(), in)
+	returnValues, callErr := callLocalMethod(method.Method(), in, false)
 
 	if callErr != nil {
 		logger.Errorf("Invoke function error: %+v, service: %#v", callErr, url)
diff --git a/proxy/proxy_factory/utils.go b/proxy/proxy_factory/utils.go
index 44af7d2..1d5a7dc 100644
--- a/proxy/proxy_factory/utils.go
+++ b/proxy/proxy_factory/utils.go
@@ -26,8 +26,9 @@
 	perrors "github.com/pkg/errors"
 )
 
-// CallLocalMethod is used to handle invoke exception in user func.
-func callLocalMethod(method reflect.Method, in []reflect.Value) ([]reflect.Value, error) {
+// callLocalMethod invokes a local method and recovers panics.
+// useCallSlice is reserved for generic calls that already carry a packed variadic tail.
+func callLocalMethod(method reflect.Method, in []reflect.Value, useCallSlice bool) ([]reflect.Value, error) {
 	var (
 		returnValues []reflect.Value
 		retErr       error
@@ -46,7 +47,11 @@
 			}
 		}()
 
-		returnValues = method.Func.Call(in)
+		if useCallSlice {
+			returnValues = method.Func.CallSlice(in)
+		} else {
+			returnValues = method.Func.Call(in)
+		}
 	}()
 
 	if retErr != nil {
diff --git a/proxy/proxy_factory/utils_test.go b/proxy/proxy_factory/utils_test.go
index 4afd7fd..95d8bcc 100644
--- a/proxy/proxy_factory/utils_test.go
+++ b/proxy/proxy_factory/utils_test.go
@@ -45,19 +45,25 @@
 	panic(123)
 }
 
+func (s *callLocalMethodSample) EchoVariadic(args ...any) []any {
+	return args
+}
+
 func TestCallLocalMethod(t *testing.T) {
 	sample := &callLocalMethodSample{}
 	cases := []struct {
-		name      string
-		method    string
-		in        []reflect.Value
-		assertErr func(t *testing.T, err error)
-		assertOut func(t *testing.T, out []reflect.Value)
+		name         string
+		method       string
+		in           []reflect.Value
+		useCallSlice bool
+		assertErr    func(t *testing.T, err error)
+		assertOut    func(t *testing.T, out []reflect.Value)
 	}{
 		{
-			name:   "call success",
-			method: "Sum",
-			in:     []reflect.Value{reflect.ValueOf(sample), reflect.ValueOf(1), reflect.ValueOf(2)},
+			name:         "call success",
+			method:       "Sum",
+			in:           []reflect.Value{reflect.ValueOf(sample), reflect.ValueOf(1), reflect.ValueOf(2)},
+			useCallSlice: false,
 			assertErr: func(t *testing.T, err error) {
 				assert.NoError(t, err)
 			},
@@ -67,29 +73,58 @@
 			},
 		},
 		{
-			name:   "panic with error",
-			method: "PanicError",
-			in:     []reflect.Value{reflect.ValueOf(sample)},
+			name:         "panic with error",
+			method:       "PanicError",
+			in:           []reflect.Value{reflect.ValueOf(sample)},
+			useCallSlice: false,
 			assertErr: func(t *testing.T, err error) {
 				assert.EqualError(t, err, "boom")
 			},
 		},
 		{
-			name:   "panic with string",
-			method: "PanicString",
-			in:     []reflect.Value{reflect.ValueOf(sample)},
+			name:         "panic with string",
+			method:       "PanicString",
+			in:           []reflect.Value{reflect.ValueOf(sample)},
+			useCallSlice: false,
 			assertErr: func(t *testing.T, err error) {
 				assert.EqualError(t, err, "boom str")
 			},
 		},
 		{
-			name:   "panic with unknown type",
-			method: "PanicUnknown",
-			in:     []reflect.Value{reflect.ValueOf(sample)},
+			name:         "panic with unknown type",
+			method:       "PanicUnknown",
+			in:           []reflect.Value{reflect.ValueOf(sample)},
+			useCallSlice: false,
 			assertErr: func(t *testing.T, err error) {
 				assert.EqualError(t, err, "invoke function error, unknow exception: 123")
 			},
 		},
+		{
+			name:         "variadic slice stays packed without call slice",
+			method:       "EchoVariadic",
+			in:           []reflect.Value{reflect.ValueOf(sample), reflect.ValueOf([]any{"alice", "bob"})},
+			useCallSlice: false,
+			assertErr: func(t *testing.T, err error) {
+				assert.NoError(t, err)
+			},
+			assertOut: func(t *testing.T, out []reflect.Value) {
+				assert.Len(t, out, 1)
+				assert.Equal(t, []any{[]any{"alice", "bob"}}, out[0].Interface())
+			},
+		},
+		{
+			name:         "variadic slice expands with call slice",
+			method:       "EchoVariadic",
+			in:           []reflect.Value{reflect.ValueOf(sample), reflect.ValueOf([]any{"alice", "bob"})},
+			useCallSlice: true,
+			assertErr: func(t *testing.T, err error) {
+				assert.NoError(t, err)
+			},
+			assertOut: func(t *testing.T, out []reflect.Value) {
+				assert.Len(t, out, 1)
+				assert.Equal(t, []any{"alice", "bob"}, out[0].Interface())
+			},
+		},
 	}
 
 	for _, tt := range cases {
@@ -98,7 +133,7 @@
 			if !ok {
 				t.Fatalf("method %s not found", tt.method)
 			}
-			out, err := callLocalMethod(m, tt.in)
+			out, err := callLocalMethod(m, tt.in, tt.useCallSlice)
 			if tt.assertErr != nil {
 				tt.assertErr(t, err)
 			}
diff --git a/server/server.go b/server/server.go
index eacb0c3..cff53f3 100644
--- a/server/server.go
+++ b/server/server.go
@@ -204,7 +204,12 @@
 	for _, arg := range args {
 		in = append(in, reflect.ValueOf(arg))
 	}
-	returnValues := method.Func.Call(in)
+	var returnValues []reflect.Value
+	if shouldUseGenericVariadicCallSlice(ctx, method, args) {
+		returnValues = method.Func.CallSlice(in)
+	} else {
+		returnValues = method.Func.Call(in)
+	}
 
 	// Process return values
 	if len(returnValues) == 1 {
@@ -229,6 +234,28 @@
 	return result, err
 }
 
+// shouldUseGenericVariadicCallSlice is the ServiceInfo reflection-side gate for
+// generic variadic calls whose tail has already been packed into the declared slice type.
+func shouldUseGenericVariadicCallSlice(ctx context.Context, method reflect.Method, args []any) bool {
+	if !method.Type.IsVariadic() || len(args) == 0 || len(args) != method.Type.NumIn()-2 {
+		return false
+	}
+
+	value, ok := ctx.Value(constant.DubboCtxKey(constant.GenericVariadicCallSliceKey)).(bool)
+	if !ok || !value {
+		return false
+	}
+
+	lastArg := args[len(args)-1]
+	if lastArg == nil {
+		return false
+	}
+
+	lastArgType := reflect.TypeOf(lastArg)
+	variadicSliceType := method.Type.In(method.Type.NumIn() - 1)
+	return lastArgType.AssignableTo(variadicSliceType) || lastArgType.ConvertibleTo(variadicSliceType)
+}
+
 // createReflectionMethodFunc creates a MethodFunc that calls the given method via reflection.
 func createReflectionMethodFunc(method reflect.Method) func(ctx context.Context, args []any, handler any) (any, error) {
 	return func(ctx context.Context, args []any, handler any) (any, error) {
diff --git a/server/server_test.go b/server/server_test.go
index edced82..9a4e309 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -33,6 +33,7 @@
 
 import (
 	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/global"
 )
 
@@ -315,6 +316,12 @@
 
 func (g *greetServiceForTest) Reference() string { return "greetServiceForTest" }
 
+type variadicReflectionServiceForTest struct{}
+
+func (s *variadicReflectionServiceForTest) HelloVariadic(ctx context.Context, prefix string, names ...string) (string, error) {
+	return prefix + ":" + strconv.Itoa(len(names)), nil
+}
+
 // TestEnhanceServiceInfoMethodFuncBackfillExactName verifies that
 // enhanceServiceInfo fills in MethodFunc when the ServiceInfo method name
 // matches the Go exported method name exactly (PascalCase).
@@ -353,6 +360,25 @@
 		"MethodFunc must be found via swapped-case lookup to avoid nil-func panic on lowercase-first method names")
 }
 
+func TestCallMethodByReflectionVariadic(t *testing.T) {
+	svc := &variadicReflectionServiceForTest{}
+	method, ok := reflect.TypeOf(svc).MethodByName("HelloVariadic")
+	require.True(t, ok)
+
+	t.Run("generic packed variadic tail uses call slice", func(t *testing.T) {
+		ctx := context.WithValue(context.Background(), constant.DubboCtxKey(constant.GenericVariadicCallSliceKey), true)
+		res, err := CallMethodByReflection(ctx, method, svc, []any{"hello", []string{"alice", "bob"}})
+		require.NoError(t, err)
+		assert.Equal(t, "hello:2", res)
+	})
+
+	t.Run("ordinary discrete variadic call remains unchanged", func(t *testing.T) {
+		res, err := CallMethodByReflection(context.Background(), method, svc, []any{"hello", "alice", "bob"})
+		require.NoError(t, err)
+		assert.Equal(t, "hello:2", res)
+	})
+}
+
 // Test getMetadataPort with default protocol
 func TestGetMetadataPortWithDefaultProtocol(t *testing.T) {
 	opts := defaultServerOptions()