This document outlines critical design constraints and best practices when developing code that runs during the Java agent's premain phase (bootstrap).
Following these guidelines will help avoid breaking applications and ensure maximum compatibility.
The Java agent runs in two distinct phases:
- premain phase: Code executed before the application's
mainmethod - post-main phase: Code executed after the application has started
The premain phase is particularly sensitive because many frameworks and applications configure their runtime environment during or after main.
Loading certain Java classes too early can lock in the wrong implementations or cause unexpected behavior.
Why to avoid:
- Some frameworks set system properties to select different JUL implementations
- Webapp servers may set these properties after
main - Using JUL during premain can cause the wrong implementation to become locked-in
- Can cause log-spam and startup delays when the chosen implementation class is not available
- May cause issues when web-apps expect to set up a context class-loader before JUL is used
What to use instead:
Logging depends on when the code runs, not where it's loaded.
Code running before the internal logger is configured (Agent.java line 154) must use System.err, not System.out, which is reserved for application output:
System.err.println("Diagnostic message"); // System.err, NOT System.outAfter the internal logger is ready, use org.slf4j that is shaded and redirects to our internal logger:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log = LoggerFactory.getLogger(MyClass.class);Why to avoid:
- Calling
FileSystems.getDefault()during premain can break applications that set thejava.nio.file.spi.DefaultFileSystemProvidersystem property in theirmainmethod - Some applications expect to control the default filesystem implementation, and premature initialization locks in the wrong provider
- Loading
java.nioalso triggers native library initialization (pthreadon Linux) which can introduce a race condition
Reference: Java NIO FileSystems Documentation
What to use instead:
- Use
java.io.*classes for file operations during premain java.io.File,FileInputStream,FileOutputStream, etc. are safe alternatives- If you must use NIO features, defer initialization until after
main
Example from issue #9780:
// BAD - Triggers java.nio initialization in premain
FileSystems.getDefault();
Path.of("/some/path");
// GOOD - Use java.io instead
new File("/some/path");
new FileInputStream(file);Why to avoid:
- Similar to JUL, some frameworks set system properties to select custom JMX builders
- These properties may be set after
main - Premature JMX initialization can lock in the wrong builder
- Can cause startup delays if the implementation class is not immediately available
What to use instead:
- Defer JMX registration and usage until after the application has started
- Initialize JMX components lazily when first needed
Guideline: Move as much code as possible out of premain
The general direction has been to minimize what runs during premain. Only execute what is absolutely necessary for:
- Setting up the instrumentation framework
- Registering transformers
- Critical initialization that must happen before application code runs
Guideline: Be extremely careful about what classes are loaded during premain
Loading a class during premain can have unintended side effects:
- Triggers static initializers
- May load related classes
- Can initialize native libraries
- May lock in system property values
Guideline: Be aware of native library loading and initialization
Some Java classes trigger native library loading:
java.nio- triggers pthread initialization on Linux and may create a race condition (race conditionJDK-8345810)- Socket operations - may trigger native networking libraries
- File system operations - platform-specific native code