apache/pulsar · error

functions may not take more than two arguments, but function

Error message

functions may not take more than two arguments, but function takes %d

What it means

validateArguments uses reflection to inspect a Go function supplied as a Pulsar Function handler. Handlers may take at most two inputs: an optional context.Context plus the message payload. A handler with NumIn() > 2 cannot be mapped and newFunction fails with this error.

Source

Thrown at pulsar-function-go/pf/function.go:65

	output, err := function(ctx, input)
	if err != nil {
		log.Errorf("process function error:[%s]\n", err.Error())
		return nil, err
	}

	return output, nil
}

func errorHandler(e error) pulsarFunction {
	return func(ctx context.Context, input []byte) ([]byte, error) {
		return nil, e
	}
}

func validateArguments(handler reflect.Type) (bool, error) {
	handlerTakesContext := false
	if handler.NumIn() > 2 {
		return false, fmt.Errorf("functions may not take more than two arguments, but function takes %d", handler.NumIn())
	} else if handler.NumIn() > 0 {
		contextType := reflect.TypeOf((*context.Context)(nil)).Elem()
		argumentType := handler.In(0)
		handlerTakesContext = argumentType.Implements(contextType)
		if handler.NumIn() > 1 && !handlerTakesContext {
			return false, fmt.Errorf("function takes two arguments, but the first is not Context. got %s", argumentType.Kind())
		}
	}

	return handlerTakesContext, nil
}

func validateReturns(handler reflect.Type) error {
	errorType := reflect.TypeOf((*error)(nil)).Elem()

	switch {
	case handler.NumOut() > 2:
		return fmt.Errorf("function may not return more than two values")

View on GitHub (pinned to 820761864e)

Solutions

  1. Reduce the handler to at most two parameters: (ctx context.Context, msg T) or (msg T).
  2. Move extra inputs into the message payload or read them from context/metadata inside the function body.
  3. Use function structs with instance state instead of extra parameters for configuration values.
  4. Wrap multi-argument logic in an adapter closure that conforms to the two-argument contract.

Example fix

// before
func handle(ctx context.Context, msg []byte, topic string) ([]byte, error) { ... }
// after
func handle(ctx context.Context, msg []byte) ([]byte, error) {
    topic := ctx.Value("topic").(string)
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

func validateHandlerSig(handler interface{}) error {
    t := reflect.TypeOf(handler)
    if t.Kind() != reflect.Func || t.NumIn() > 2 {
        return errors.New("handler must take at most two arguments")
    }
    return nil
}
// call before registering: validateHandlerSig(myHandler)

Type guard

func isValidHandler(handler interface{}) bool {
    t := reflect.TypeOf(handler)
    return t != nil && t.Kind() == reflect.Func && t.NumIn() <= 2
}

Try / catch

f, err := pf.NewGoFunction(ctx, pf.GoFunction{
    Handler: myHandler,
})
if err != nil {
    log.Fatalf("function registration failed: %v", err)
}

Prevention

When it happens

Trigger: Registering a handler via pf.NewGoFunction (or the instance path in newFunction) whose signature has three or more input parameters, e.g. func(ctx context.Context, msg []byte, extra string) ([]byte, error).

Common situations: Developers coming from other frameworks assume extra parameters (message ID, properties, logger) can be injected; refactoring adds a parameter for convenience; generic helper functions reused as handlers.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/49cfa4e7fc48d28c. Report an issue: GitHub.