docs/GettingStarted.md
BTrace is a safe, dynamic tracing tool for the Java platform. It allows you to dynamically instrument running Java applications without stopping them, recompiling code, or adding logging statements. BTrace works by injecting tracing code into the bytecode of target applications at runtime.
Use BTrace when you need to:
Download the latest release from GitHub releases
Extract the distribution:
# For .tar.gz
tar -xzf btrace-<version>.tar.gz
# For .zip
unzip btrace-<version>.zip
Set environment variables (optional but recommended):
export BTRACE_HOME=/path/to/btrace
export PATH=$BTRACE_HOME/bin:$PATH
RPM-based systems (RedHat, CentOS, Fedora):
sudo rpm -i btrace-<version>.rpm
Debian-based systems (Ubuntu, Debian):
sudo dpkg -i btrace-<version>.deb
JBang makes it incredibly easy to use BTrace without manual installation. It automatically downloads and caches BTrace from Maven Central.
Install JBang (one time):
# macOS / Linux
curl -Ls https://sh.jbang.dev | bash -s - app setup
# Windows (PowerShell)
iex "& { $(iwr https://ps.jbang.dev) } app setup"
# Or use package managers
brew install jbangdev/tap/jbang # macOS
sdk install jbang # SDKMAN
Use BTrace with JBang (no separate BTrace installation needed):
# Attach to running application (replace <version> with desired version, e.g., 2.3.0)
jbang io.btrace:btrace-client:<version> <PID> <script.java>
# Add the BTrace JBang catalog (one time), then use the shorter alias
jbang catalog add --name btraceio https://raw.githubusercontent.com/btraceio/jbang-catalog/main/jbang-catalog.json
jbang btrace@btraceio <PID> <script.java>
Extract agent JARs (if needed for --agent-jar/--boot-jar flags):
# Extract to a directory of your choice
jbang io.btrace:btrace-client:<version> --extract-agent ~/.btrace
# This creates:
# ~/.btrace/btrace-agent.jar
# ~/.btrace/btrace-boot.jar
# Then use them explicitly:
jbang btrace@btraceio --agent-jar ~/.btrace/btrace-agent.jar \
--boot-jar ~/.btrace/btrace-boot.jar \
<PID> <script.java>
Alternative: Use JARs from Maven local repository:
After jbang downloads BTrace, find the JARs in your local Maven repository (default ~/.m2):
# JARs are cached at:
~/.m2/repository/io/btrace/btrace-agent/<version>/btrace-agent-<version>.jar
~/.m2/repository/io/btrace/btrace-boot/<version>/btrace-boot-<version>.jar
# Use them directly:
jbang btrace@btraceio --agent-jar ~/.m2/repository/io/btrace/btrace-agent/<version>/btrace-agent-<version>.jar \
--boot-jar ~/.m2/repository/io/btrace/btrace-boot/<version>/btrace-boot-<version>.jar \
<PID> <script.java>
Benefits:
btrace -h
# or with JBang
jbang btrace@btraceio -h
You should see the BTrace help message with available options.
New in BTrace: DTrace-style oneliners let you debug without writing script files!
# Find your Java application's PID
jps
# Trace method entry with arguments
btrace -n 'TestApp::processData @entry { print method, args }' <PID>
# Find slow methods (>50ms)
btrace -n 'TestApp::* @return if duration>50ms { print method, duration }' <PID>
# Count method invocations
btrace -n 'TestApp::doWork @entry { count }' <PID>
# Print stack traces
btrace -n 'TestApp::processData @entry { stack(5) }' <PID>
Oneliner Syntax:
class-pattern::method-pattern @location [filter] { action }
@entry, @return, @errorprint, count, time, stackif duration>NUMBERms, if args[N]==VALUEFor complete oneliner documentation, see Oneliner Guide.
Want full BTrace power? Continue to the full 5-minute quick start below.
Let's trace a simple Java application to see BTrace in action with full Java scripts.
Create a simple Java program (TestApp.java):
public class TestApp {
public static void main(String[] args) throws Exception {
System.out.println("TestApp started. Press Enter to begin...");
System.in.read();
while (true) {
doWork();
Thread.sleep(1000);
}
}
private static void doWork() {
String result = processData("example", 42);
System.out.println("Processed: " + result);
}
private static String processData(String name, int value) {
return name + "-" + value;
}
}
Compile and run it:
javac TestApp.java
java TestApp
Create a BTrace script (TraceMethods.java) to trace method calls:
import org.openjdk.btrace.core.annotations.BTrace;
import org.openjdk.btrace.core.annotations.OnMethod;
import static org.openjdk.btrace.core.BTraceUtils.println;
import static org.openjdk.btrace.core.BTraceUtils.str;
@BTrace
public class TraceMethods {
@OnMethod(clazz = "TestApp", method = "processData")
public static void onProcessData(String name, int value) {
println("Called processData: name=" + name + ", value=" + str(value));
}
}
Find the process ID of your TestApp:
jps
Output will show something like:
12345 TestApp
12346 Jps
Attach BTrace:
btrace 12345 TraceMethods.java
Press Enter in the TestApp window to start processing
Observe the output in the BTrace terminal:
Called processData: name=example, value=42
Called processData: name=example, value=42
...
Detach BTrace: Press Ctrl+C in the BTrace terminal and type exit
Congratulations! You've successfully traced your first Java application with BTrace.
Capture latency distributions and simple stats without external systems using the built-in histogram metrics extension (HdrHistogram-based).
BTRACE_HOME/extensions/.MetricsService (no special flags needed):import static org.openjdk.btrace.core.BTraceUtils.*;
import org.openjdk.btrace.core.annotations.*;
import org.openjdk.btrace.metrics.MetricsService;
import org.openjdk.btrace.metrics.histogram.*;
import org.openjdk.btrace.metrics.stats.*;
@BTrace
public class LatencyProbe {
@Injected
private static MetricsService metrics;
private static HistogramMetric h;
private static StatsMetric s;
@OnMethod(clazz = "TestApp", method = "processData")
public static void onEntry() {
if (h == null) {
h = metrics.histogramMicros("testapp.process");
s = metrics.stats("testapp.process.stats");
}
}
@OnMethod(clazz = "TestApp", method = "processData", location = @Location(Kind.RETURN))
public static void onReturn(@Duration long durNs) {
long us = durNs / 1000;
h.record(us);
s.record(us);
}
@OnTimer(1000)
public static void report() {
HistogramSnapshot hs = h.snapshot();
StatsSnapshot ss = s.snapshot();
println("=== Metrics Report ===");
println("Count: " + ss.count());
println("Mean: " + ss.mean() + " μs");
println("Min: " + ss.min() + " μs");
println("Max: " + ss.max() + " μs");
println("P50: " + hs.p50() + " μs");
println("P95: " + hs.p95() + " μs");
println("P99: " + hs.p99() + " μs");
println("======================");
}
}
btrace <PID> LatencyProbe.java
See the full tutorial section: “Using the Histogram Metrics Extension (btrace-metrics)” in docs/BTraceTutorial.md for configuration and details.
BTrace offers multiple deployment modes to suit different use cases:
Use JBang to run BTrace without installation:
jbang io.btrace:btrace-client:<version> <PID> <script.java>
# One-time catalog setup for the short alias
jbang catalog add --name btraceio https://raw.githubusercontent.com/btraceio/jbang-catalog/main/jbang-catalog.json
When to use:
Examples:
# Basic usage
jbang btrace@btraceio 12345 MyTrace.java
# With verbose output
jbang btrace@btraceio -v 12345 MyTrace.java arg1 arg2
# Extract agent JARs, then use them explicitly
jbang btrace@btraceio --extract-agent ~/.btrace
jbang btrace@btraceio --agent-jar ~/.btrace/btrace-agent.jar \
--boot-jar ~/.btrace/btrace-boot.jar \
12345 MyTrace.java
# Or use JARs from Maven local repository (after jbang downloads them)
jbang btrace@btraceio --agent-jar ~/.m2/repository/io/btrace/btrace-agent/<version>/btrace-agent-<version>.jar \
--boot-jar ~/.m2/repository/io/btrace/btrace-boot/<version>/btrace-boot-<version>.jar \
12345 MyTrace.java
Benefits:
Attach to an already running Java process:
btrace [options] <PID> <script.java> [script-args]
When to use:
Example:
btrace -v 12345 MyTrace.java arg1 arg2
Common options:
-v - Verbose output-p <port> - Specify port for communication-o <file> - Redirect output to file--agent-jar <path> - Override agent JAR auto-discovery--boot-jar <path> - Override boot JAR auto-discoveryStart a Java application with BTrace agent and a pre-compiled script:
java -javaagent:btrace-agent.jar=script=<script.class>[,arg1=value1]... YourApp
When to use:
Example:
# First compile the script
btracec MyTrace.java
# Then run with agent
java -javaagent:/path/to/btrace-agent.jar=script=MyTrace.class MyApp
Use the btracer wrapper to compile and attach in one step:
btracer <script.class> <java-app-with-args>
When to use:
Example:
# First compile the script
btracec MyTrace.java
# Launch app with trace
btracer MyTrace.class java -cp myapp.jar com.example.Main
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class MethodEntry {
@OnMethod(clazz = "com.example.MyClass", method = "myMethod")
public static void onEntry() {
println("Method called!");
}
}
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class MethodArgs {
@OnMethod(clazz = "com.example.MyClass", method = "calculate")
public static void onCalculate(int x, int y) {
println("calculate called with: x=" + str(x) + ", y=" + str(y));
}
}
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class MethodReturn {
@OnMethod(clazz = "com.example.MyClass", method = "calculate", location = @Location(Kind.RETURN))
public static void onReturn(@Return int result) {
println("calculate returned: " + str(result));
}
}
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class MethodDuration {
@OnMethod(clazz = "com.example.MyClass", method = "slowMethod")
public static void onEntry(@Duration long durationNanos) {
if (durationNanos > 0) {
println("slowMethod took: " + str(durationNanos / 1000000) + " ms");
}
}
}
BTrace integrates with Java Flight Recorder (JFR) to create high-performance events with <1% overhead. JFR events are recorded natively by the JVM and can be analyzed with JDK Mission Control.
Requirements: OpenJDK 8 (with backported JFR) or Java 11+ (not available in Java 9-10)
import org.openjdk.btrace.core.annotations.*;
import org.openjdk.btrace.core.jfr.JfrEvent;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class MyJfrTrace {
// Define JFR event factory
@Event(
name = "MethodCall",
label = "Method Call Event",
description = "Tracks method calls with duration",
category = {"myapp", "performance"},
fields = {
@Event.Field(type = Event.FieldType.STRING, name = "method"),
@Event.Field(type = Event.FieldType.LONG, name = "duration")
}
)
private static JfrEvent.Factory callEventFactory;
@TLS private static long startTime;
@OnMethod(clazz = "com.example.MyClass", method = "process")
public static void onEntry() {
startTime = timeNanos();
}
@OnMethod(clazz = "com.example.MyClass", method = "process",
location = @Location(Kind.RETURN))
public static void onReturn(@ProbeMethodName String method) {
// Create and commit JFR event
JfrEvent event = Jfr.prepareEvent(callEventFactory);
if (Jfr.shouldCommit(event)) {
Jfr.setEventField(event, "method", method);
Jfr.setEventField(event, "duration", timeNanos() - startTime);
Jfr.commit(event);
}
}
}
After running the script, JFR events are recorded in the flight recorder:
# Run BTrace script
btrace <PID> MyJfrTrace.java
# Start flight recording (if not already running)
jcmd <PID> JFR.start name=my-recording
# Dump recording to file
jcmd <PID> JFR.dump name=my-recording filename=recording.jfr
# Analyze with Mission Control
jmc recording.jfr
Benefits over println:
For complete JFR documentation, see BTrace Tutorial Lesson 5 and FAQ: JFR Integration.
BTrace works in containerized environments with some considerations.
Attach to running container:
# Find container ID/name
docker ps
# Execute BTrace in container
docker exec -it <container-id> btrace <PID> script.java
Prerequisites:
Example Dockerfile with official BTrace images:
# Option 1: Copy BTrace into your application image (recommended)
FROM btrace/btrace:latest AS btrace
FROM bellsoft/liberica-openjdk-debian:11-cds
COPY --from=btrace /opt/btrace /opt/btrace
ENV BTRACE_HOME=/opt/btrace
ENV PATH=$PATH:$BTRACE_HOME/bin
# Your application...
COPY target/myapp.jar /app/
ENTRYPOINT ["java", "-jar", "/app/myapp.jar"]
Alternative: Manual installation (if not using official images):
FROM bellsoft/liberica-openjdk-debian:11-cds
RUN curl -L https://github.com/btraceio/btrace/releases/download/v2.2.2/btrace-2.2.2.tar.gz \
| tar -xz -C /opt/
ENV BTRACE_HOME=/opt/btrace-2.2.2
ENV PATH=$PATH:$BTRACE_HOME/bin
See docker/Readme.md for more Docker usage patterns.
Attach to pod:
# Find pod and process ID
kubectl get pods
kubectl exec <pod-name> -- jps
# Run BTrace
kubectl exec -it <pod-name> -- btrace <PID> script.java
Copy script to pod first (if needed):
kubectl cp MyTrace.java <pod-name>:/tmp/
kubectl exec -it <pod-name> -- btrace <PID> /tmp/MyTrace.java
Trace multiple pods:
# Get all pods for a deployment
PODS=$(kubectl get pods -l app=myapp -o jsonpath='{.items[*].metadata.name}')
# Attach to each pod
for POD in $PODS; do
echo "Tracing $POD..."
kubectl exec $POD -- btrace 1 script.java &
done
Add BTrace as a sidecar container for persistent availability:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
shareProcessNamespace: true # Important: enables cross-container process visibility
containers:
- name: app
image: myapp:latest
- name: btrace
image: btrace/btrace:latest-alpine # Official BTrace Alpine image
command: ["/bin/sh", "-c", "while true; do sleep 30; done"]
volumeMounts:
- name: btrace-scripts
mountPath: /scripts
volumes:
- name: btrace-scripts
configMap:
name: btrace-scripts
Note: Requires shareProcessNamespace: true to allow sidecar to see app container processes.
Using the sidecar:
# Execute BTrace from sidecar
kubectl exec <pod-name> -c btrace -- btrace $(pgrep -f myapp) /scripts/trace.btrace
# View output
kubectl logs <pod-name> -c btrace
jps or ps aux to find Java process ID-p flag if neededFor comprehensive troubleshooting, see Troubleshooting: Kubernetes.
Problem: Unable to attach to target VM
Solutions:
-XX:+DisableAttachMechanism (remove it)-XX:+EnableDynamicAgentLoading to target JVM to suppress warnings and ensure compatibilityNote: Starting with JDK 21, dynamic agent loading triggers warnings. In a future JDK release, it will be disabled by default, requiring -XX:+EnableDynamicAgentLoading to use BTrace's attach mode. See Troubleshooting: JVM Attachment Issues for details.
Problem: BTrace script verification failed
Common causes:
Solution: Use only BTrace-safe operations from BTraceUtils class.
Problem: Script attaches but produces no output
Checklist:
/com\\.example\\..*/ not /com.example.*/println() is imported from BTraceUtilsProblem: Script doesn't match any classes
Solutions:
"com.example.MyClass" not "MyClass"$: "com.example.Outer$Inner""/com\\.example\\..*/"Problem: BTrace slows down the application
Solutions:
@Sampled annotation@Level annotationProblem: Garbled output with special characters
Solution: Set encoding:
btrace -Dfile.encoding=UTF-8 <PID> script.java
Now that you have BTrace running, explore these resources: