Skip to content

InjectorHolder builds its injector during class initialization, so one failed build breaks it for the rest of the JVM #376

Open

Description

@maximthomas

InjectorHolder builds its injector in the enum constructor, that is during class initialization, from the GuiceModuleLoader that InjectorConfiguration holds at that moment. It never reads the loader again. If that build throws, the IllegalStateException fails class initialization. Every later use of the class in the same JVM then throws NoClassDefFoundError: Could not initialize class org.forgerock.guice.core.InjectorHolder, without the original cause.

GuiceTestCase.setupGuiceModules() triggers that build too. It calls InjectorHolder.getInjector() only to save oldInjector before it registers the test's own injector, and that first call initializes the class. So a GuiceTestCase subclass depends on whichever loader was installed when the first class in the JVM touched the holder, although it never uses the root injector.

In OpenAM this makes test results depend on class order wherever the default loader cannot build an injector from the test classpath (in openam-scripting it fails with No implementation for NotificationBroker was bound). Each such test module has to install a loader before any class touches the holder: a @BeforeSuite hook in openam-core-rest (OpenIdentityPlatform/OpenAM#1186) and a TestNG execution listener in openam-scripting (OpenIdentityPlatform/OpenAM#1188). The openam-core-rest failure only showed up on CI after ubuntu-latest moved to ubuntu-26.04, which runs the test classes in a different order.

A possible direction:

  • build the root injector lazily, on the first getInstance, injectMembers or createChildInjector call that finds no injector, instead of in the constructor;
  • have getInjector() return the current injector without building one, so GuiceTestCase saves and restores "no injector" instead of forcing a build;
  • report a failed build to each caller with its cause, instead of failing class initialization.

With that, a GuiceTestCase subclass would no longer depend on the loader at all, and a failed root build would show its real cause.

Activity

  1. self-assigned this
    on Oct 20, 2026
  2. added a commit that references this issue on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugjavaPull requests that update java code

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions