testcontainers/testcontainers-java · error · java.lang.IllegalArgumentException

Configured Image Substitutor could not be loaded

Error message

Configured Image Substitutor could not be loaded: ${configuredClassName}

What it means

ImageNameSubstitutor.getImageNameSubstitutor loads a custom substitutor class named by testcontainers.image.name.substitutor in ~/.testcontainers.properties. If the class cannot be loaded, instantiated via a no-arg constructor, or cast to ImageNameSubstitutor, it throws IllegalArgumentException including the configured class name. This is a config/classpath problem surfaced at first image-name resolution.

Solutions

  1. Verify the class name in ~/.testcontainers.properties matches a class on the test classpath, spelled correctly.
  2. Ensure the class extends ImageNameSubstitutor and has a public no-arg constructor.
  3. Remove the testcontainers.image.name.substitutor property if you don't need a custom substitutor.
  4. Check the cause chain in the stack trace for the underlying load/instantiation failure (ClassNotFound vs Instantiation vs constructor exception).

Example fix

// before (testcontainers.properties)
testcontainers.image.name.substitutor=com.example.OldSubstitutor
// after
testcontainers.image.name.substitutor=com.example.MyImageNameSubstitutor // existing, public, no-arg ctor
Defensive patterns

Strategy: validation

Validate before calling

String cls = TestcontainersConfiguration.getInstance().getImageSubstitutorClassName();
if (cls != null) {
    Class<?> c = Class.forName(cls);
    if (!ImageNameSubstitutor.class.isAssignableFrom(c)) throw new IllegalStateException(cls + " must extend ImageNameSubstitutor");
    c.getDeclaredConstructor().setAccessible(true);
}

Try / catch

try {
    ImageNameSubstitutor sub = ImageNameSubstitutor.instance();
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Configured Image Substitutor could not be loaded")) {
        log.error("Check testcontainers.image.name.substitutor: {}", e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: Setting testcontainers.image.name.substitutor=fully.qualified.ClassName where the class is not on the test classpath, lacks a public no-arg constructor, fails in its constructor/static init, or doesn't extend ImageNameSubstitutor.

Common situations: Typo in the fully-qualified class name; class moved/renamed after a refactor; config set locally but class lives only in another module; dependency not declared in the test classpath; constructor doing DI-dependent work.

Related errors


AI-assisted analysis of testcontainers/testcontainers-java@8e549514e3 (2026-09-12). Data as JSON: /api/errors/dba4ee57e41ff70c. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/testcontainers/utility/ImageNameSubstitutor.java:68

                    );
            } else {
                instance = wrapWithLogging(defaultImplementation);
            }

            log.info("Image name substitution will be performed by: {}", instance.getDescription());
        }

        return instance;
    }

    private static ImageNameSubstitutor getImageNameSubstitutor(ClassLoader classLoader) {
        final String configuredClassName = TestcontainersConfiguration.getInstance().getImageSubstitutorClassName();

        if (configuredClassName != null) {
            try {
                return (ImageNameSubstitutor) classLoader.loadClass(configuredClassName).getConstructor().newInstance();
            } catch (Exception e) {
                throw new IllegalArgumentException(
                    "Configured Image Substitutor could not be loaded: " + configuredClassName,
                    e
                );
            }
        }

        return StreamSupport
            .stream(ServiceLoader.load(ImageNameSubstitutor.class, classLoader).spliterator(), false)
            .findFirst()
            .orElse(null);
    }

    public static ImageNameSubstitutor noop() {
        return new NoopImageNameSubstitutor();
    }

    private static ImageNameSubstitutor wrapWithLogging(final ImageNameSubstitutor wrappedInstance) {
        return new LogWrappedImageNameSubstitutor(wrappedInstance);

View on GitHub (pinned to 8e549514e3)