Dependency Injection 103 — Guice vs. Dagger

AJ Ribeiro
Bigeye
Published in
4 min readAug 4, 2020

Background

In Part 1 of this series we discussed the Dependency Injection (DI) pattern and what its benefits are. In Part 2, we took a deep dive into how DI tooling can speed up development, specifically focusing on Guice. In Part 3, we aim to give a brief overview of the other modern Java DI library, Dagger, and peek into the internals of both Dagger and Guice. Note that what I am referring to as Dagger is actually Dagger 2, which is Google’s fork of the original Dagger library, which came from Square.

Dagger Overview

Dagger, like Guice, provides an API which allows us to express our dependency graph. The API is very similar to Guice, and allows for field injection, constructor injection, and the provider pattern using @Inject and @Provides annotations the same way that Guice does (albeit with different implementations of the annotations). Although Dagger shares many similarities with Guice, Dagger also differs from Guice in some key ways:

1. Dagger doesn’t have shortcuts for binding interfaces to implementations like Guice does, rather Dagger requires providers for these bindings

2. Dagger doesn’t automatically inject zero-argument constructors

3. Dagger doesn’t have analogs for the createInjector() and getInstance() methods of Guice, but rather requires the definition of a Component interface

4. Dagger doesn’t allow injection of constructors which throw exceptions

5. Dagger doesn’t allow for cyclic dependencies

Baeldung has a great in-depth guide of how to instrument your code with Dagger. A full working example of the application used in Parts 1 and 2 instrumented with Dagger can be found on Github. Constructor injection, field injection, and the provider pattern are all illustrated.

Although not strictly true, Dagger’s API is very close to being a subset of Guice’s, and in many cases, both can achieve the same end result. When making a decision between which libraries to use, it really comes down to the use case.

How Dagger works

Dagger works by generating the code at build time. Specifically, it runs an annotation processor which looks for @Inject, @Provides, @Module, and @Component annotations to construct the dependency graph. The annotation processor then generates Java code which will be used to create the necessary objects. These generated files include a component class which gives us our top level component(s), e.g. a House in the Github example, and factories for all of our injectable classes. These factories are used by the generated Component code to instantiate its dependencies. If you clone and build the Github example, you can see the generated code in /build/generated/sources/annotationProcessor/java.

How Guice works

In contrast to Dagger, which generates code at build time, Guice uses reflection at runtime. Recall from our House example that in our main application we call Guice.createInjector(new AppModule()). When we call that method, Guice reflects back onto our AppModule, creates the dependency graph, and uses its annotations to determine how to construct all of the dependencies needed to give our application the classes which we inject into it.

Guice or Dagger

The answer to this question really depends on your use case, and can depend on whether you are working on a Java or Android application. If you’re working on an Android application, reflection is very slow. This means that using Guice could have a noticeable effect on performance and Dagger is probably the right answer for you.

If you’re working on a Java application, then your options are more open. Dagger running at build time can be attractive, because it’s generally preferable to break at build time over run-time in the event of a mistake in your DI code. However, this might be a minor issue if your Guice injectors are created early in an application’s lifecycle. If this is the case, your application should still fail fast.

The answer of Guice vs. Dagger in Java applications boils down to if you would rather have the performance boost associated with Dagger due to code generation and a minimal API, or the extra features Guice provides us with. These features include interface/implementation binding shortcuts, zero-arg constructor injection, no need for a component interface, constructors that throw exceptions, and cyclic dependencies.

We at Toro Data still favor Guice in our Java apps, due to the additional features it provides. In particular we use the binding shortcuts frequently. In reality, you can’t go wrong with either of these libraries. They both offer terrific functionality, as well as a straightforward mechanism to level-up your codebase and improve developer productivity.

--

--