Dependency injection in Go is a divisive topic. Half the community swears by constructor injection and nothing else; the other half has spent years untangling init() functions, package-level globals, and hand-written wiring code that breaks the moment a constructor signature changes. If you have ever added a parameter to NewClient() and watched the compiler surface the same fix in six different files, you already know the pain. Three tools promise to automate that wiring: Google Wire, Uber Fx, and Uber Dig. In 2026, though, one of them comes with a warning label you should not ignore.

TL;DR: Quick Verdict

  • Choose Google Wire if you want compile-time safety, zero runtime reflection, and generated code you can read. But be aware it is officially unmaintained — the README now says so — so budget for a fork or a migration path.
  • Choose Uber Fx if you want a full application framework with lifecycle hooks, graceful shutdown, and a battle-tested runtime. It is the backbone of nearly all Go services at Uber and the safest long-term bet for production.
  • Choose Uber Dig only if you are building your own framework or need a bare reflection-based container for startup-time wiring. Do not use it as a service locator.

If you are starting a new project today, Fx is the default recommendation — active maintenance, SemVer-stable v1, and lifecycle management that Wire and Dig do not give you.

Comparison at a Glance

FeatureGoogle WireUber FxUber Dig
ApproachCompile-time code generationRuntime DI + application frameworkReflection-based DI container
Type safetyCompile-time (generated code)Runtime (errors on Invoke)Runtime (errors on Invoke)
Stars14,4117,6404,494
Last updateAug 2025Dec 2025May 2025
Maintenance status⚠️ Unmaintained (README notice)Active (v1, SemVer)Active (v1, SemVer)
Lifecycle hooksNoYes (OnStart/OnStop)No
Code generationYes (wire_gen.go)NoNo
Runtime overheadNoneReflection + containerReflection + container
Best forConfig-heavy services, teams that read generated codeFull applications, servers, CLIs with graceful shutdownFramework builders, plugin systems
LicenseApache-2.0MITMIT

Decision Matrix: Which Tool for Your Use Case?

Use CaseRecommended ToolWhy
New production service, need lifecycle + graceful shutdownFxfx.Lifecycle hooks handle start/stop ordering and clean teardown out of the box
You hate runtime errors and want wiring failures at go build timeWireGenerated code makes broken graphs a compile error, not a panic at boot
Building your own framework or app skeleton on top of a containerDigFx is literally built on Dig; it gives you Provide/Invoke without framework opinions
A small service with 3–5 dependencies and no framework ambitionsNone — plain constructorsHonest answer: adding a DI tool is overhead you do not need yet
Legacy Wire codebase that must keep runningWire (fork or pinned)The unmaintained notice does not break existing code, but pin the version and plan a migration
Plugin systems, dynamic registration, decoratorsDigDecorate lets you wrap existing values at runtime — Wire cannot do this at all

Google Wire: Compile-Time DI You Can Read

Wire’s model is fundamentally different from the other two: you write a small injector file declaring the providers, and the wire CLI generates plain Go code that wires everything for you. There is no reflection, no runtime container, and no global state — the generated wire_gen.go is ordinary Go that reads exactly like hand-written initialization.

Install and write your providers first:

1
go install github.com/google/wire/cmd/wire@latest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// providers.go
package main

type Message struct{ text string }

func NewMessage() Message { return Message{text: "Hello, Wire!"} }

type Greeter struct{ Message Message }

func NewGreeter(m Message) Greeter { return Greeter{Message: m} }

type Event struct{ Greeter Greeter }

func NewEvent(g Greeter) Event { return Event{Greeter: g} }

Then declare the graph in a build-tagged file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// wire.go
//go:build wireinject
// +build wireinject

package main

func InitializeEvent() Event {
	wire.Build(NewEvent, NewGreeter, NewMessage)
	return Event{}
}

Run wire ./... and it generates wire_gen.go with the wiring spelled out:

1
2
3
4
5
6
7
8
// Code generated by Wire. DO NOT EDIT.

func InitializeEvent() Event {
	message := NewMessage()
	greeter := NewGreeter(message)
	event := NewEvent(greeter)
	return event
}

The appeal is obvious: if a provider set is missing or a dependency is unsatisfiable, wire fails during generation — never at runtime. The generated code is debuggable, readable, and has zero reflection cost. Teams that value explicitness love this.

The catch in 2026: the project is no longer maintained. The README carries an explicit notice telling users to fork if they want changes. Wire still works — it is a code generator, so it does not rot as quickly as a runtime library — but you will not get new features, Go-version compatibility fixes, or security patches. For a long-lived production codebase, that is a real risk to price in.

Uber Fx: The Application Framework

Fx is the highest-level of the three: a dependency injection system and an application framework. It wraps Dig under the hood and adds lifecycle management, so your wiring graph and your process lifecycle live in one place.

1
go get go.uber.org/fx@v1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"context"
	"fmt"
	"net/http"

	"go.uber.org/fx"
)

type Handler struct{}

func NewHandler() *Handler { return &Handler{} }

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello, Fx!")
}

func main() {
	app := fx.New(
		fx.Provide(NewHandler),
		fx.Invoke(func(mux *http.ServeMux, h *Handler) {
			mux.Handle("/", h)
		}),
		fx.NopLogger,
	)
	app.Run()
}

The lifecycle hooks are where Fx earns its keep:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fx.New(
	fx.Provide(NewServer),
	fx.Invoke(func(lc fx.Lifecycle, srv *http.Server) {
		lc.Append(fx.Hook{
			OnStart: func(ctx context.Context) error {
				go srv.ListenAndServe()
				return nil
			},
			OnStop: func(ctx context.Context) error {
				return srv.Shutdown(ctx)
			},
		})
	}),
	fx.NopLogger,
).Run()

Fx’s README states the pitch plainly: eliminate globals, remove init() and package-level state, and let teams compose shareable, loosely coupled components. It is “the backbone of nearly all Go services at Uber” — that is not marketing boilerplate; it means the framework’s rough edges have been sanded off by years of production traffic. The v1 line follows strict SemVer, and the project tracks the Go release policy.

The trade-off: everything happens at runtime. A missing provider fails on fx.New() or Invoke, not at compile time, and the reflection-based graph can make stack traces less obvious. Fx also drags in opinions — lifecycle hooks, fx.Options modules, fx.NopLogger to silence its chatter — which is great for a server and overkill for a tiny CLI.

Uber Dig: The Bare Reflection Container

Dig is the foundation under Fx: a reflection-based toolkit with two operations that matter — Provide (register a constructor) and Invoke (resolve the graph). Its own README is unusually honest about scope: it is good for powering an application framework like Fx and resolving the object graph at process startup; it is bad for replacing an application framework, resolving dependencies after startup, or being exposed to user-land code as a service locator.

1
go get go.uber.org/dig@v1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"go.uber.org/dig"
)

func main() {
	c := dig.New()

	c.Provide(NewMessage)
	c.Provide(NewGreeter)
	c.Provide(NewEvent)

	if err := c.Invoke(func(e Event) {
		e.Greeter.Greet()
	}); err != nil {
		panic(err)
	}
}

Dig’s differentiator is Decorate — you can wrap or replace a value already in the container, which is how Fx implements cross-cutting concerns and how plugin systems swap implementations at runtime. Wire, being a static generator, simply cannot express “replace the existing database client with a cached one at boot.”

Where Dig hurts: errors are strings at runtime (could not build arguments for function ...: missing type: *Config), and a typo in a constructor parameter surfaces when the process starts, not when CI runs. If you only need 5 dependencies wired, Dig is a heavy hammer. If you are building a framework, it is exactly the right primitive — which is why Fx sits on top of it.

Common Pitfalls and Migration Notes

1. Wire is unmaintained — plan around it. Existing Wire projects are not suddenly broken, and the generator’s output remains valid Go. But pin your Wire version, vendor it if your pipeline allows, and start a migration ticket. The safest path is incremental: keep generated wire_gen.go in the repo (it is plain code!), then replace it with hand-written constructors or Fx wiring section by section.

2. Dig as a service locator is an anti-pattern. The Dig README warns against it explicitly. If your application code calls c.Invoke in handlers to grab dependencies, you have a hidden global with extra steps — your functions no longer declare their inputs. Keep Dig usage at the composition root.

3. Runtime graph errors are the price of reflection. With Wire, a broken graph fails codegen. With Dig/Fx, it fails at boot. Mitigation: add a startup smoke test that runs fx.New(...) (or a full Invoke) in CI, so graph breakage fails fast in a test, not in production.

4. Fx logs are noisy — use fx.NopLogger. By default Fx logs every provide/invoke event. In tests and quiet CLIs, pass fx.NopLogger as an option; it eliminates the chatter without disabling lifecycle behavior.

5. Mixing Fx with init() defeats the purpose. The whole point is eliminating global state. If you keep init()-registered side effects (database pools, config loading) outside the graph, you end up with two initialization paths that can execute in any order.

6. Don’t wire what you can construct. A struct with two plain dependencies does not need a container. DI tools pay off at 10+ components with shared interfaces; below that they add indirection without subtracting complexity. The decision matrix above is not being cute — “use plain constructors” is a legitimate, frequently correct choice.

7. Migration from Wire to Fx is mostly mechanical. Wire’s provider functions are plain functions — Fx’s Provide accepts them unchanged. Your wire.Build set becomes a list of fx.Provide calls; InitializeEvent() becomes fx.Invoke(func(e Event) {...}). The main work is moving lifecycle logic (previously manual go func() { srv.ListenAndServe() }()) into fx.Lifecycle hooks.

Why Dependency Injection Still Matters in 2026

The arguments against DI in Go are familiar: the language gives you interfaces and constructor injection, so why add machinery? The counter-argument is scale. A service with 15 components — config, logger, database, cache, queues, two API clients, three background workers — needs somebody to order their construction, and hand-written wiring makes every new dependency a multi-file edit. DI tools centralize that graph in one place, make it inspectable, and (with Fx) attach lifecycle semantics to it.

The ecosystem has also settled. In 2024–2026 the discussion moved from “is DI idiomatic in Go?” to “which DI strategy for which codebase,” and the answer above — codegen for compile-time safety, framework for applications, bare container for frameworks — is now the consensus. If you are evaluating this for a real service, also look at how your team debugs: engineers who reach for dlv and want to step through wiring prefer Wire’s generated code; engineers who think in terms of application lifecycles prefer Fx.

FAQ

Is Google Wire still maintained in 2026? No. The official README states the project is no longer maintained and recommends forking for updates. The generator still works and produced code remains valid Go, but there are no new features or compatibility fixes — pin the version and plan a migration.

What is the difference between Fx and Dig? Dig is a low-level reflection-based DI container with Provide/Invoke/Decorate. Fx is an application framework built on Dig that adds lifecycle hooks (OnStart/OnStop), modules, and logging. If you need a container, use Dig; if you need an application skeleton, use Fx.

Does Wire have runtime overhead? No. Wire generates plain Go code at build time, so the running program contains the same initialization you would write by hand — no reflection, no container, no global state.

When should I avoid DI tools in Go? For small services with a handful of dependencies, hand-written constructors are simpler and more readable. DI tools earn their keep when the component graph grows past ~10 items or when you need lifecycle management, decorators, or test-time substitution of interfaces.

Can I use Fx with gin or chi routers? Yes — Fx is framework-agnostic. You Provide your router and handlers, then use fx.Invoke to register routes or mount the handler on an http.ServeMux. For more on Go web frameworks, see our gin vs echo vs fiber comparison and the Go CLI libraries guide.

How does Fx handle graceful shutdown? fx.Lifecycle hooks registered with Append run in order on start and in reverse order on stop. app.Run() blocks until a signal arrives, then invokes all OnStop hooks — making srv.Shutdown(ctx) the natural place to drain connections.

Is Dig a service locator? It can be, if you misuse it. The official README explicitly says Dig is bad for exposing dependencies to user-land code as a service locator. Keep all Invoke calls at the composition root.

Which Go DI tool has the most stars? Google Wire leads with 14,411 stars, followed by Uber Fx (7,640) and Uber Dig (4,494). Star count does not equal maintenance status here — Wire is unmaintained despite the lead, while both Uber libraries are actively maintained under strict SemVer v1.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com