Generic invocation is a special way of calling in Dubbo-Go that allows intermediary nodes to pass call information without interface information, commonly used in testing and gateway scenarios. Generic invocation supports both Dubbo and Triple protocols. When creating a generic service via client.NewGenericService, Hessian2 is used as the default transport serialization.
For better understanding, this document introduces generic invocation with a gateway usage scenario. Let’s first consider a normal invocation (non-generic invocation). The diagram below includes two key roles: consumer and provider (hereinafter referred to as endpoint, representing either a consumer or a provider), each having a definition of the org.apache.dubbo.sample.User interface. Suppose the org.apache.dubbo.sample.User interface needs to be used in the calling behavior.

RPC requires transmission over a network medium; therefore, data cannot be transmitted as go struct and must be transmitted in binary form. This requires that the consumer side serialize the struct implementing the org.apache.dubbo.sample.User interface into binary format before transmission. Likewise, for the provider side, the binary data needs to be deserialized into struct information. In summary, normal invocation requires that interface information must have the same definition at each endpoint to ensure that the results of serialization and deserialization meet expectations.
In gateway scenarios, it is impractical for the gateway to store all interface definitions. For example, if a gateway needs to forward 100 service calls, each requiring 10 interfaces, normal invocation would necessitate storing 1000 (100 * 10) interface definitions in advance, which is clearly difficult. So is there a way that does not require prior storage of interface definitions while still forwarding calls correctly? The answer is yes, which is the reason for using generic invocation.
Generic invocation essentially transforms a complex structure into a generic structure, where the generic structure refers to maps, strings, etc., which the gateway can smoothly parse and pass.

Dubbo-go v3 supports multiple generic invocation modes, including Map (default), Gson, Protobuf-JSON, and Bean. Among these, Map is the most commonly used. The following uses it as an example. Taking the User interface as an example, its definition is as follows:
// definition
type User struct {
ID string
Name string
Age int32
}
func (u *User) JavaClassName() string {
return "org.apache.dubbo.sample.User"
}
Suppose a service call requires a user as an input parameter, which is defined as follows:
// an instance of the User
user := &User{
ID: "1",
Name: "Zhangsan",
Age: 20,
}
Then under the Map generic invocation, the user will be automatically converted to Map format as follows:
usermap := map[interface{}]interface{} {
"iD": "1",
"name": "zhangsan",
"age": 20,
"class": "org.apache.dubbo.sample.User",
}
Note that:
m struct tag. For example, Name string `m:"userName"` will set the key to userName instead of the default name;hessian.POJO interface, a class field will be automatically inserted into the generalized result to identify the original interface class. This behavior can be disabled by setting generic.include.class=false, which is suitable for generic invocation between pure Go services.Generic invocation is transparent to the provider side, meaning the provider does not need any explicit configuration to handle generic requests correctly.
The Triple protocol (dubbo3) supports generic invocation. NewGenericService uses a unified dial flow internally, supporting both service discovery via a registry and direct connection via client.WithURL. For a complete example, see dubbo-go-samples/generic. The following uses direct connection as an example.
First, create a dubbo instance and a client, then create a generic service via cli.NewGenericService, as shown below.
ins, err := dubbo.NewInstance(dubbo.WithName("generic-client"))
cli, err := ins.NewClient(
client.WithClientProtocolTriple(),
client.WithClientSerialization(constant.Hessian2Serialization),
)
genericService, err := cli.NewGenericService(
"org.apache.dubbo.samples.UserProvider",
client.WithURL("tri://127.0.0.1:50052"),
client.WithVersion("1.0.0"),
client.WithGroup("triple"),
)
The NewGenericService method accepts the following parameters:
client.WithURL: direct connection address, using the tri:// protocol prefix;client.WithVersion: service version;client.WithGroup: service group.Then you can make a generic invocation on the service:
resp, err := genericService.Invoke(
context.Background(),
"GetUser1",
[]string{"java.lang.String"},
[]hessian.Object{"A003"},
)
The generic invocation based on Filter is transparent to the consumer, and a typical application scenario is a gateway. This method requires that the Dubbo URL contains a generic invocation identifier, as follows.
dubbo://127.0.0.1:20000/org.apache.dubbo.sample.UserProvider?generic=true&...
The semantics expressed by this Dubbo URL are:
The consumer’s Filter will automatically convert normal calls to generic calls based on the configuration carried by the Dubbo URL. When inv.Reply() is set to a target struct pointer, the consumer-side Filter will automatically deserialize the map result into the corresponding object during the OnResponse phase; if not set, the response will still be returned in generic format (e.g., map).
It is recommended to use
client.NewGenericServiceas described above. The following is the legacy approach based onconfig.ReferenceConfig, provided for reference only.
Manual generic invocation constructs a reference via config.ReferenceConfig and explicitly initiates a generic invocation, a typical application scenario for testing.
Generic invocation does not require creating a configuration file (dubbogo.yaml), but requires manual configuration of the registry, reference, and other information in the code. The initialization method is encapsulated in the newRefConf method, as follows.
func newRefConf(appName, iface, protocol string) config.ReferenceConfig {
registryConfig := &config.RegistryConfig{
Protocol: "zookeeper",
Address: "127.0.0.1:2181",
}
refConf := config.ReferenceConfig{
InterfaceName: iface,
Cluster: "failover",
Registry: []string{"zk"},
Protocol: protocol,
Generic: "true",
}
rootConfig := config.NewRootConfig(config.WithRootRegistryConfig("zk", registryConfig))
_ = rootConfig.Init()
_ = refConf.Init(rootConfig)
refConf.GenericLoad(appName)
return refConf
}
The newRefConf method takes three parameters:
In the above method, for simplicity, the registry is set as a fixed value, using ZooKeeper at 127.0.0.1:2181 as the registry, which can be customized according to actual conditions in practice.
We can easily obtain a ReferenceConfig instance, temporarily named refConf.
refConf := newRefConf("example.dubbo.io", "org.apache.dubbo.sample.UserProvider", "tri")
Next, we can invoke the GetUser method of the org.apache.dubbo.sample.UserProvider service using generic invocation.
resp, err := refConf.
GetRPCService().(*generic.GenericService).
Invoke(
context.TODO(),
"GetUser",
[]string{"java.lang.String"},
[]hessian.Object{"A003"},
)
The Invoke method of GenericService takes four parameters:
[]string{"type1", "type2", ...}. If the method is parameterless, you need to fill in an empty array []string{};[]hessian.Object{} as a placeholder.In the Invoke method, the return value resp is a map type (for MapGeneralizer), requiring users to manually perform type conversion. To simplify this process, dubbo-go provides the InvokeWithType method, which can automatically deserialize the result into a user-specified struct.
The following example uses the client.NewGenericService approach:
// Define the result struct
type User struct {
ID string
Name string
Age int32
}
var user User
// Deserialize the result directly into the user struct
err := genericService.InvokeWithType(
context.Background(),
"GetUser1",
[]string{"java.lang.String"},
[]hessian.Object{"A003"},
&user,
)
if err != nil {
// Handle error
}
fmt.Println(user.Name)
When using the legacy config.ReferenceConfig approach, the calling method is as follows:
var user User
err := refConf.
GetRPCService().(*generic.GenericService).
InvokeWithType(
context.TODO(),
"GetUser",
[]string{"java.lang.String"},
[]hessian.Object{"A003"},
&user,
)
if err != nil {
// Handle error
}
fmt.Println(user.Name)
Note: InvokeWithType currently only supports the default Map generic invocation.
Dubbo-go supports the following four generic invocation modes, distinguished by the generic parameter value:
| Mode | Parameter Value | Generalized Format | Use Case |
|---|---|---|---|
| Map (default) | true | map[string]any | General purpose, Go and Java interoperability |
| Gson | gson | JSON string | Scenarios requiring JSON format interaction |
| Protobuf-JSON | protobuf-json | JSON string | Generic invocation of Protobuf messages |
| Bean | bean | JavaBeanDescriptor | Alignment with Java Bean serialization |
When using client.NewGenericService, the Map mode is used by default. To use other modes, set the Generic field in ReferenceConfig to the corresponding parameter value.
In most scenarios, the default Map generic mode is recommended.
In generic invocation or cross-language scenarios, Go variadic method signatures such as args ...string can introduce contract ambiguity.
Existing variadic services remain supported, but ...T is not recommended in new RPC contracts for cross-language or generic-invocation-facing services.
The reason is that variadic parameters have special semantics in Go, while cross-language invocation, dynamic argument assembly, generic invocation, and serialization usually represent them as ordinary arrays or lists. This increases the risk that callers and providers interpret the argument shape differently.
For new service contracts, prefer the following patterns:
[]T instead of ...T for repeated values of the same typerepeated fieldsAvoid introducing new cross-language contracts like this:
func MultiArgs(ctx context.Context, args ...string) error
Prefer a slice parameter instead:
func MultiArgs(ctx context.Context, args []string) error
If the contract may evolve further, prefer a request object:
type MultiArgsRequest struct {
Args []string
}
func MultiArgs(ctx context.Context, req *MultiArgsRequest) error
For new Triple + Protobuf services, use a repeated field to model repeated inputs:
message MultiArgsRequest {
repeated string args = 1;
}
Existing variadic services remain supported. This guidance is mainly intended for new service contract design. If a service is expected to support cross-language consumers, gateway forwarding, or generic invocation, it is better not to expose variadic parameters as part of the public RPC contract.
In general, business inputs should be modeled explicitly as structured fields. Non-business context should be passed through attachments or IDL metadata instead of variadic parameters.
Related reading: 【Dubbo-go Service Proxy Model】