{"record":{"id":"098c3afd2daf4590","repo":"grpc/grpc-go","slug":"xdsclient-eventhandler-is-nil","errorCode":null,"errorMessage":"xdsclient: eventHandler is nil","messagePattern":"xdsclient: eventHandler is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/clients/xdsclient/channel.go","lineNumber":88,"sourceCode":"\teventHandler       xdsChannelEventHandler  // Callbacks for ADS stream events.\n\tbackoff            func(int) time.Duration // Backoff function to use for stream retries. Defaults to exponential backoff, if unset.\n\twatchExpiryTimeout time.Duration           // Timeout for ADS resource watch expiry.\n\tlogPrefix          string                  // Prefix to use for logging.\n}\n\n// newXDSChannel creates a new xdsChannel instance with the provided options.\n// It performs basic validation on the provided options and initializes the\n// xdsChannel with the necessary components.\nfunc newXDSChannel(opts xdsChannelOpts) (*xdsChannel, error) {\n\tswitch {\n\tcase opts.transport == nil:\n\t\treturn nil, errors.New(\"xdsclient: transport is nil\")\n\tcase opts.serverConfig == nil:\n\t\treturn nil, errors.New(\"xdsclient: serverConfig is nil\")\n\tcase opts.clientConfig == nil:\n\t\treturn nil, errors.New(\"xdsclient: clientConfig is nil\")\n\tcase opts.eventHandler == nil:\n\t\treturn nil, errors.New(\"xdsclient: eventHandler is nil\")\n\t}\n\n\txc := &xdsChannel{\n\t\ttransport:    opts.transport,\n\t\tserverConfig: opts.serverConfig,\n\t\tclientConfig: opts.clientConfig,\n\t\teventHandler: opts.eventHandler,\n\t\tclosed:       syncutil.NewEvent(),\n\t}\n\n\tl := grpclog.Component(\"xds\")\n\tlogPrefix := opts.logPrefix + fmt.Sprintf(\"[xds-channel %p] \", xc)\n\txc.logger = igrpclog.NewPrefixLogger(l, logPrefix)\n\n\tif opts.backoff == nil {\n\t\topts.backoff = backoff.DefaultExponential.Backoff\n\t}\n\tnp := internal.NodeProto(opts.clientConfig.Node)","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/clients/xdsclient/channel.go#L70-L106","documentation":"The final mandatory field checked by `newXDSChannel` (channel.go:79) is `eventHandler` — an implementation of the xdsChannelEventHandler interface that receives callbacks for ADS stream events (resource updates, errors, resource-does-not-exist). Because the channel has no other way to deliver these events, a nil handler is rejected at channel.go:87-88.","triggerScenarios":"Triggered when `newXChannel` is called with `opts.eventHandler == nil`. The XDSClient normally supplies an authority (which implements the interface) as the handler; a nil means the wiring was skipped.","commonSituations":"Calling newXDSChannel from a test stub without supplying an event handler; refactoring the authority wiring so the handler is no longer passed; a custom resource-type integration that bypasses the authority abstraction.","solutions":["Pass a non-nil xdsChannelEventHandler (typically the owning authority) in xdsChannelOpts.eventHandler.","For tests, supply a no-op handler struct that satisfies the interface rather than leaving it nil.","Audit any custom code that constructs xdsChannelOpts to confirm eventHandler is set."],"exampleFix":"// before\nopts := xdsChannelOpts{transport: t, serverConfig: sc, clientConfig: cc}\nxc, err := newXDSChannel(opts) // err: eventHandler is nil\n\n// after\ntype noopHandler struct{}\nfunc (noopHandler) OnResourceUpdate(rt ResourceType, n string, d xdsresource.ResourceData) {}\nfunc (noopHandler) OnResourceError(rt ResourceType, n string, err error) {}\nfunc (noopHandler) OnStreamError(err error) {}\nopts := xdsChannelOpts{transport: t, serverConfig: sc, clientConfig: cc, eventHandler: noopHandler{}}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Compile-time assertion that your handler satisfies the interface.\nvar _ xdsChannelEventHandler = (*myHandler)(nil)\n\ntype myHandler struct{}\nfunc (*myHandler) OnResourceUpdate(rt ResourceType, n string, d xdsresource.ResourceData) {}\nfunc (*myHandler) OnResourceError(rt ResourceType, n string, err error)                 {}\nfunc (*myHandler) OnStreamError(err error)                                            {}\nfunc (*myHandler) adsResourceDoesNotExist(rt ResourceType, n string)                   {}","tryCatchPattern":null,"preventionTips":["Always supply a non-nil event handler (or the owning authority) when constructing a channel.","For tests, use a tiny no-op handler struct rather than nil.","Use compile-time interface checks (var _ Interface = ...) to catch signature drift early."],"tags":["grpc","xds","xdschannel","config-validation","event-handler"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}