Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misleading exception message for nested Guice injector failure #1761

Open
toddliebenschutz-jones opened this issue Aug 16, 2023 · 1 comment

Comments

@toddliebenschutz-jones
Copy link

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.

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.<init>(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.<init>(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.<init>(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.<init>(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).

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.<init>(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.<init>(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.
@lobaorn
Copy link

lobaorn commented Jun 1, 2024

Hey @toddliebenschutz-jones , letting you know since you may be targeting Guice 7.0.0, about the new Micronaut Guice project, that depending on your usecase could be a useful replacement: micronaut-projects/micronaut-guice#9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants