-
Notifications
You must be signed in to change notification settings - Fork 1.7k
BINDING_ALREADY_SET
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.
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.
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.
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
inBarModule
so thatFoo
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 bindFoo
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.
-
User's Guide
-
Integration
-
Extensions
-
Internals
-
Releases
-
Community