micro/go-micro · error

argument type not exported

Error message

%v argument type not exported: %v

What it means

During subscriber registration, go-micro's gRPC server validates that every handler method's single argument type is either a builtin or an exported type. If the message type passed to the subscriber method is unexported (e.g. starts with a lowercase letter) the server cannot reliably encode/decode or introspect it, so Subscribe fails with this error instead of registering the subscriber.

Solutions

  1. Rename the handler's argument struct to start with an uppercase letter so it is exported
  2. Move the message type to a shared package and export it
  3. If the argument is a Go builtin (string, []byte, map, etc.) no change is needed — check that you are not wrapping it in an unexported alias

Example fix

// before
type handler struct{}
func (h *handler) Handle(evt event) error { ... } // 'event' unexported
// after
type Event struct{ ... }
func (h *handler) Handle(evt *Event) error { ... }
Defensive patterns

Strategy: validation

Validate before calling

func validSubscriberArg(fn interface{}) bool {
	t := reflect.TypeOf(fn)
	if t.NumIn() != 3 { return false } // receiver, ctx, msg
	arg := t.In(2)
	if arg.Kind() == reflect.Ptr { arg = arg.Elem() }
	return arg.PkgPath() == "" || arg.Name()[0] >= 'A' && arg.Name()[0] <= 'Z' // builtin or exported
}

Prevention

When it happens

Trigger: Calling server.Subscribe() with a Subscriber whose handler method signature is like `func (s *Handler) handleMsg(msgType)` where `msgType` is an unexported (lowercase) struct type defined in the handler's package.

Common situations: Developers define message structs in a private package or accidentally lowercase the message type name; generated protobuf types are exported but hand-written internal event types often are not.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/f7978b41715d1f00. Report an issue: GitHub.

Appendix: source

Thrown at server/grpc/subscriber.go:153

			return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
		}
	} else {
		hdlr := reflect.ValueOf(sub.Subscriber())
		name := reflect.Indirect(hdlr).Type().Name()

		for m := 0; m < typ.NumMethod(); m++ {
			method := typ.Method(m)

			switch method.Type.NumIn() {
			case 3:
				argType = method.Type.In(2)
			default:
				return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
					name, method.Name, method.Type.NumIn(), subSig)
			}

			if !isExportedOrBuiltinType(argType) {
				return fmt.Errorf("%v argument type not exported: %v", name, argType)
			}
			if method.Type.NumOut() != 1 {
				return fmt.Errorf(
					"subscriber %v.%v has wrong number of outs: %v require signature %s",
					name, method.Name, method.Type.NumOut(), subSig)
			}
			if returnType := method.Type.Out(0); returnType != typeOfError {
				return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
			}
		}
	}

	return nil
}

func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler {
	return func(p broker.Event) (err error) {
		defer func() {

View on GitHub (pinned to 24529f1404)