google / google/guice

Misleading exception message for nested Guice injector failure

Open
#1,761 1 comment 1 reaction 0 assignees View on GitHub
Dominant language
Java
Stars
12.7k
Forks
1.7k
Avg merge
11m
Merged PRs (30d)
2

Description

When using nested Guice injectors, the exception message makes it look like any error in the inner injector is occurring in the outer injector.

## Example 1

In this code, each test attempts to create a Guice injector that cannot be created.

- `createNonNestedModule` creates a Guice injector with a missing dependency
- `createNestedModule` creates a Guice injector whose only module `ModuleWithNestedGuiceInjector` throws when created. This module `ModuleWithNestedGuiceInjector` throws because it creates a nested Guice injector with a missing dependency.

These two failures are from two fundamentally different Guice injectors failing to start up (i.e. the nested injector, or the outer injector), however the exception message does not allow them to be distinguished.

```java
package com.example;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.junit.jupiter.api.Test;

class DemoTest {

static final class MyModule extends AbstractModule {

@Override
protected void configure() {
bind(MyClass.class);
}
}

@Singleton
static final class MyClass {

@Inject
MyClass(MyInterface myInterface) {
}
}

interface MyInterface {

}

@Test
void createNonNestedModule() {
Guice.createInjector(new MyModule());
}

@Test
void createNestedModule() {
Guice.createInjector(new ModuleWithNestedGuiceInjector());
}

static class ModuleWithNestedGuiceInjector extends AbstractModule {

@Override
protected void configure() {
Guice.createInjector(new MyModule());
}
}
}
```


Non-nested exception message

```

Unable to create injector, see the following errors:

1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound.

Requested by:
1 : DemoTest$MyClass.(DemoTest.java:23)
\_ for 1st parameter myInterface
at DemoTest$MyModule.configure(DemoTest.java:15)

Learn more:
https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION

1 error

======================
Full classname legend:
======================
DemoTest$MyClass: "com.example.DemoTest$MyClass"
DemoTest$MyInterface: "com.example.DemoTest$MyInterface"
DemoTest$MyModule: "com.example.DemoTest$MyModule"
========================
End of classname legend:
========================

com.google.inject.CreationException: Unable to create injector, see the following errors:

1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound.

Requested by:
1 : DemoTest$MyClass.(DemoTest.java:23)
\_ for 1st parameter myInterface
at DemoTest$MyModule.configure(DemoTest.java:15)

Learn more:
https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION

1 error

======================
Full classname legend:
======================
DemoTest$MyClass: "com.example.DemoTest$MyClass"
DemoTest$MyInterface: "com.example.DemoTest$MyInterface"
DemoTest$MyModule: "com.example.DemoTest$MyModule"
========================
End of classname legend:
========================

at app//com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:576)
at app//com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:163)
at app//com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
at app//com.google.inject.Guice.createInjector(Guice.java:87)
at app//com.google.inject.Guice.createInjector(Guice.java:69)
at app//com.google.inject.Guice.createInjector(Guice.java:59)
at app//com.example.DemoTest.createNonNestedModule(DemoTest.java:37)
at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568)
at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)
```


Nested exception message

```

Unable to create injector, see the following errors:

1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound.

Requested by:
1 : DemoTest$MyClass.(DemoTest.java:23)
\_ for 1st parameter myInterface
at DemoTest$MyModule.configure(DemoTest.java:15)

Learn more:
https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION

1 error

======================
Full classname legend:
======================
DemoTest$MyClass: "com.example.DemoTest$MyClass"
DemoTest$MyInterface: "com.example.DemoTest$MyInterface"
DemoTest$MyModule: "com.example.DemoTest$MyModule"
========================
End of classname legend:
========================

com.google.inject.CreationException: Unable to create injector, see the following errors:

1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound.

Requested by:
1 : DemoTest$MyClass.(DemoTest.java:23)
\_ for 1st parameter myInterface
at DemoTest$MyModule.configure(DemoTest.java:15)

Learn more:
https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION

1 error

======================
Full classname legend:
======================
DemoTest$MyClass: "com.example.DemoTest$MyClass"
DemoTest$MyInterface: "com.example.DemoTest$MyInterface"
DemoTest$MyModule: "com.example.DemoTest$MyModule"
========================
End of classname legend:
========================

at app//com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:576)
at app//com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:163)
at app//com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
at app//com.google.inject.Guice.createInjector(Guice.java:87)
at app//com.google.inject.Guice.createInjector(Guice.java:69)
at app//com.google.inject.Guice.createInjector(Guice.java:59)
at app//com.example.DemoTest.createNestedModule(DemoTest.java:42)
at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568)
at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)

```

## Example 2

The second example shows why this is confusing in practice. The exception message does not allow the user to work out which injector has the missing dependency. The exception messages for these two test are the same (apart from the test method in the stack trace).

```java
package com.example;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.junit.jupiter.api.Test;

class DemoTest {

static final class MyModule extends AbstractModule {

private final boolean missingDependency;

public MyModule(boolean missingDependency) {
this.missingDependency = missingDependency;
}

@Override
protected void configure() {
if (missingDependency) {
bind(MyClass.class);
}
}
}

@Singleton
static final class MyClass {

@Inject
MyClass(MyInterface myInterface) {
}
}

interface MyInterface {

}

@Test
void createInjectorWithMissingDependencyInNestedInjector() {
Guice.createInjector(new MyModule(true),
new ModuleWithNestedGuiceInjector(false));
}

@Test
void createInjectorWithMissingDependency() {
Guice.createInjector(new ModuleWithNestedGuiceInjector(true),
new MyModule(false));
}

static class ModuleWithNestedGuiceInjector extends AbstractModule {

private final boolean missingDependency;

ModuleWithNestedGuiceInjector(boolean missingDependency) {
this.missingDependency = missingDependency;
}

@Override
protected void configure() {
Guice.createInjector(new MyModule(missingDependency));
}
}
}
```

Exception message

```
Unable to create injector, see the following errors:

1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound.

Requested by:
1 : DemoTest$MyClass.(DemoTest.java:31)
\_ for 1st parameter myInterface
at DemoTest$MyModule.configure(DemoTest.java:22)

Learn more:
https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION

1 error

======================
Full classname legend:
======================
DemoTest$MyClass: "com.example.DemoTest$MyClass"
DemoTest$MyInterface: "com.example.DemoTest$MyInterface"
DemoTest$MyModule: "com.example.DemoTest$MyModule"
========================
End of classname legend:
========================

com.google.inject.CreationException: Unable to create injector, see the following errors:

1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound.

Requested by:
1 : DemoTest$MyClass.(DemoTest.java:31)
\_ for 1st parameter myInterface
at DemoTest$MyModule.configure(DemoTest.java:22)

Learn more:
https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION

1 error

======================
Full classname legend:
======================
DemoTest$MyClass: "com.example.DemoTest$MyClass"
DemoTest$MyInterface: "com.example.DemoTest$MyInterface"
DemoTest$MyModule: "com.example.DemoTest$MyModule"
========================
End of classname legend:
========================

at app//com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:576)
at app//com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:163)
at app//com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
at app//com.google.inject.Guice.createInjector(Guice.java:87)
at app//com.google.inject.Guice.createInjector(Guice.java:69)
at app//com.google.inject.Guice.createInjector(Guice.java:59)
at app//com.example.DemoTest.createNonNestedModule(DemoTest.java:41)
at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568)
at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)

```

## Other notes

- I've tested the above examples in Guice 6.0.0 and 7.0.0.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.