01
Search earlier than the repeated error
Locate the first exception for the affected class, including its cause chain and the context that triggered initialization. Repeated “Could not initialize class” messages can be downstream symptoms of an earlier failure rather than evidence of a missing JAR.
Our fixture read demo.endpoint while initializing a static field. Because the property was absent, load threw IllegalStateException. The first access surfaced ExceptionInInitializerError; later use of the erroneous class failed differently.
02
Reproduce the lifecycle in one JVM
Save the following diagnostic probe as InitializationProbe.java. It intentionally catches LinkageError so it can display both attempts. That catch is for the experiment; it is not a recommendation for a production service to swallow initialization failures.
The probe changes the property after the first attempt and immediately retries access. This makes the distinction between repaired input and a previously failed class initialization visible without relying on an application framework.
class StaticConfig {
static final String ENDPOINT = load();
private static String load() {
String value = System.getProperty("demo.endpoint");
if (value == null) throw new IllegalStateException("demo.endpoint is missing");
return value;
}
}
public class InitializationProbe {
private static void attempt() {
try {
System.out.println("endpoint=" + StaticConfig.ENDPOINT);
} catch (LinkageError error) { // Diagnostic probe only.
System.out.println(error.getClass().getSimpleName() + ":" + error.getMessage());
if (error.getCause() != null) {
System.out.println("cause=" + error.getCause().getClass().getSimpleName());
}
}
}
public static void main(String[] args) {
attempt();
System.setProperty("demo.endpoint", "https://example.invalid");
attempt();
}
}03
Compare with a fresh process using valid input
Run once without the property, then in a separate JVM with the property supplied at startup. In our test, only the fresh valid process printed the endpoint on both accesses. Changing the property after the first failure did not restart initialization in the original loader.
In production, fix the configuration source and use the application’s normal recovery or deployment lifecycle. Repeated restarts without correcting the cause only repeat the failing initialization.
javac -d out InitializationProbe.java
java -cp out InitializationProbe
java -Ddemo.endpoint=https://example.invalid -cp out InitializationProbe04
Make fallible configuration loading explicit
When you control the design, load and validate configuration before constructing the application components that use it. Ordinary method failure has a clearer recovery contract than hiding fallible external configuration behind a static field initializer.
The following small example checks only presence. It demonstrates normal exception handling and a subsequent valid load; it does not validate a real endpoint’s syntax, connectivity, credentials, or health. Save it as ExplicitConfig.java.
public class ExplicitConfig {
record Config(String endpoint) {}
static Config load(String configured) {
if (configured == null || configured.isBlank()) {
throw new IllegalArgumentException("demo.endpoint is required");
}
return new Config(configured);
}
public static void main(String[] args) {
boolean rejected = false;
try { load(null); }
catch (IllegalArgumentException expected) { rejected = true; }
if (!rejected) throw new AssertionError("Missing configuration was accepted");
Config config = load("https://example.invalid");
System.out.println("explicit config=" + config.endpoint());
}
}05
Verify both failure reporting and recovery
Compile and run ExplicitConfig to check that missing input is rejected and a later valid input can be loaded normally. For the application, test its real configuration mechanism and startup validation separately.
Retain the original cause, class name, triggering operation, and recovery result. A valid class file, a fixed environment value, and a successfully initialized application are different observations. Confirm the final one through the application lifecycle instead of assuming that changing input repaired every cached failure.
Know the limits
Reproduced with Eclipse Temurin OpenJDK 25.0.4.1+1 in a disposable Linux container with no external network. A missing system property caused ExceptionInInitializerError. After setting it in the same JVM, a second access produced NoClassDefFoundError. Supplying it before a fresh JVM started worked; explicit configuration loading also recovered normally. Framework reload behavior was not tested.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles