Skip to content

BINDING_ALREADY_SET

Googler edited this page Aug 14, 2020 · 1 revision

BINDING_ALREADY_SET

Summary

Guice will throw a BINDING_ALREADY_SET error if there are multiple bindings configured for a single Guice key. This is like adding duplicate keys into a map (see Guice mental model) and when that happens Guice does not know which one to use for creating a particular object.

Example:

final class BarModule extends AbstractModule {
  @Provides
  Foo provideFoo() {
    return FooFactory.createFoo();
  }
}

final class BazModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(Foo.class).to(FooImpl.class);
  }
}

In the above example, Foo is bound to different implementations, one using FooFactory.createFoo(), the other using an injectable constructor from FooImpl class. When both modules are installed in the same injector, Guice will throw a CreationException with BINDING_ALREADY_SET error.

Common Causes

Installing a module more than once

One simple cause of this error is when a module is installed more than once in an injector. If that is the case, the recommended fix is to only install the module once at a higher level:

Before:

final class BarModule extends AbstractModule {
  @Override
  protected void configure() {
    install(new FooModule());
    ...
  }
}

final class BazModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
    install(new FooModule());
  }
}

final class ApplicationModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
    install(new BarModule());
    install(new BazModule());
  }
}

After:

final class BarModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
  }
}

final class BazModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
  }
}

final class ApplicationModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
    install(new FooModule());
    install(new BarModule());
    install(new BazModule());
  }
}

In a real application, the refactoring might be more involved especially if either BarModule or BazModule is used in other injectors (for example in tests) that assume the module also transitively provides bindings defined in FooModule. But the general approach is the same: modules should be granular so that they can be assembled in different combinations.

Using same Guice key for different implementations

Guice keys must be unique within an injector. So if you do need more than one implementation of a particular type, use binding annotations to give different keys to each implementation:

final class BarModule extends AbstractModule {
  @Provides
  @Bar
  Foo provideFoo() {
    return FooFactory.createFoo();
  }
}

final class BazModule extends AbstractModule {
  @Provides
  @Baz
  Foo provideFoo(FooImpl fooImpl) {
    return fooImpl;
  }
}

With the above code, BarModule and BazModule now both binds an implementation of Foo but with different keys.

TIP: Use Java visibility modifiers to control what Guice keys are visible outside of a module or package. For example, if a binding is not meant to be used by other modules then consider using a private annotation as part of the Guice key, which will prevent Guice key collision caused BINDING_ALREADY_SET errors.

Replacing bindings in tests

Often developers need to swap out a dependency for a fake or mock implementation in tests. However, if both the real dependency and fake/mock are bound in the test injector, Guice will throw BINDING_ALREADY_SET error.

For example, if Bar is the class under test and it has a dependency on Foo:

final class Bar {
  @Inject
  Bar(Foo foo, Baz) {
    ...
  }

  public BarResult calculate() {
    // Foo is used here.
    ...
  }
}

final class BarModule extends AbstractModule {
  @Override
  protected void configure() {
    install(new FooModule()); // FooModule provides a binding for Foo.
    ...
  }

  @Provides
  Baz provideBaz() {
    ...
  }
}

If the real Foo is not suitable to use in tests (for example it connects to a real database that is not available in tests) then to write a test for Bar, one might use a fake implementation of Foo in test like:

@RunWith(JUnit4.class)
public final class BarTest {
  private Bar bar;

  @Before
  public void setUpBar() {
    bar = Guice.createInjector(new BarModule(), new AbstractModule() {
      @Provides
      Foo provideFoo() {
        return new FakeFoo();
      }
    }).getInstance(Bar.class);
  }

  ...
}

The above code will cause a BINDING_ALREADY_SET error because the test now have two implementations of Foo installed: one from FooModule and one from the test module that returns FakeFoo.

A real test will likely be much more complicated and less obvious than this example, especially when there are multiple modules installed at different layers. But the errors have same root cause: dependencies are hard coded and tightly coupled together, making it hard to swap out the dependency in tests or in different injectors.

There are several ways to fix the error in those cases:

  • (recommended) For simple unit test, use constructor injection and construct the object manually (also known as manually dependency injection) in tests:

    @RunWith(JUnit4.class)
    public final class BarTest {
      private Bar bar;
    
      @Before
      public void setUpBar() {
        bar = new Bar(new FakeFoo(), new FakeBar());
      }
    
      ...
    }

    This method works well if you also follow best practice to only inject direct dependencies.

  • Like the duplicate module installation section above: don't install FooModule in BarModule so that Foo isn't hard coded to the real implementation:

    final class BarModule extends AbstractModule {
      @Override
      protected void configure() {
        ... // FooModule is not installed here but at a higher level.
      }
    
      @Provides
      Baz provideBaz() {
        ...
      }
    }

    With this change in BarModule, you can bind Foo to a fake in test without conflict.

TIP: BoundFieldModule makes it easy to bind fake implementations and mocks in tests.

WARNING: It's often tempting to use Modules.override to override one or more bindings in an injector, but that is not recommended. Modules.override is a crutch and is usually used to workaround modules that are not granular enough and can lead to environments that are confusing and hard to maintain. Instead of using Modules.override, consider refactoring your modules to smaller and more granular modules that can be assembled in different ways.

Clone this wiki locally