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:
- Debug production issues without redeploying
- Profile application performance in real-time
- Track method calls, arguments, and return values
- Monitor memory allocations and object creation
- Investigate thread behavior and synchronization
- Capture stack traces at specific points
- Java 8 or higher (BTrace supports Java 8-20)
- Basic knowledge of Java programming
- Target Java application running with appropriate permissions
-
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>.rpmDebian-based systems (Ubuntu, Debian):
sudo dpkg -i btrace-<version>.debJBang 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 # SDKMANUse 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:
- No manual download or installation
- Automatic version management via Maven coordinates
- Works across all platforms (Windows, macOS, Linux)
- Perfect for CI/CD pipelines and containers
btrace -h
# or with JBang
jbang btrace@btraceio -hYou 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 }
- Locations:
@entry,@return,@error - Actions:
print,count,time,stack - Filters:
if duration>NUMBERms,if args[N]==VALUE
For 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 TestAppCreate 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+Cin the BTrace terminal and typeexit
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).
- Ensure you built the distribution so extensions are available under
BTRACE_HOME/extensions/. - Create a probe that injects
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("======================");
}
}- Attach to your running app:
btrace <PID> LatencyProbe.javaSee 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.jsonWhen to use:
- Quick start without installation
- CI/CD pipelines
- Trying BTrace for the first time
- Containers and cloud environments
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.javaBenefits:
- Zero installation required
- Works everywhere (Windows, macOS, Linux, containers)
- Automatic version management
- Perfect for reproducible builds
Attach to an already running Java process:
btrace [options] <PID> <script.java> [script-args]When to use:
- Debugging production issues
- Ad-hoc performance analysis
- You don't want to restart the application
Example:
btrace -v 12345 MyTrace.java arg1 arg2Common 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-discovery
Start a Java application with BTrace agent and a pre-compiled script:
java -javaagent:btrace-agent.jar=script=<script.class>[,arg1=value1]... YourAppWhen to use:
- Tracing from application startup
- Capturing initialization issues
- Controlled environments
Example:
# First compile the script
btracec MyTrace.java
# Then run with agent
java -javaagent:/path/to/btrace-agent.jar=script=MyTrace.class MyAppUse the btracer wrapper to compile and attach in one step:
btracer <script.class> <java-app-with-args>When to use:
- Local development and testing
- Quick experiments
- You have control over application launch
Example:
# First compile the script
btracec MyTrace.java
# Launch app with trace
btracer MyTrace.class java -cp myapp.jar com.example.Mainimport 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.jfrBenefits over println:
- <1% overhead vs. 1-50% for println
- Native JVM recording (no string formatting)
- Can be analyzed offline
- Timeline visualization in Mission Control
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.javaPrerequisites:
- JDK (not JRE) must be installed in container
- BTrace must be available in container or mounted
- Same user permissions as target JVM
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/binSee 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.javaCopy script to pod first (if needed):
kubectl cp MyTrace.java <pod-name>:/tmp/
kubectl exec -it <pod-name> -- btrace <PID> /tmp/MyTrace.javaTrace 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 &
doneAdd 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-scriptsNote: 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- PID Discovery: Use
jpsorps auxto find Java process ID - Port Conflicts: BTrace uses port 2020 by default; use
-pflag if needed - Security Policies: Pod Security Policies may block ptrace; adjust as needed
- Resource Limits: BTrace overhead may trigger CPU/memory limits
For comprehensive troubleshooting, see Troubleshooting: Kubernetes.
Problem: Unable to attach to target VM
Solutions:
- Ensure BTrace and target app run as the same user
- JDK 8-20: Check if target JVM has
-XX:+DisableAttachMechanism(remove it) - JDK 21+: Add
-XX:+EnableDynamicAgentLoadingto target JVM to suppress warnings and ensure compatibility - Verify JDK (not JRE) is installed
Note: 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:
- Using forbidden operations (creating new threads, I/O operations)
- Calling non-BTrace methods
- Using synchronization primitives
Solution: Use only BTrace-safe operations from BTraceUtils class.
Problem: Script attaches but produces no output
Checklist:
- Verify class and method names are correct (case-sensitive)
- Check if the method is actually being called in the target app
- Use regular expressions carefully:
/com\\.example\\..*/not/com.example.*/ - Ensure
println()is imported fromBTraceUtils
Problem: Script doesn't match any classes
Solutions:
- Use fully qualified class names:
"com.example.MyClass"not"MyClass" - For inner classes use
$:"com.example.Outer$Inner" - Test with wildcards:
"/com\\.example\\..*/"
Problem: BTrace slows down the application
Solutions:
- Use sampling:
@Sampledannotation - Add level filtering:
@Levelannotation - Limit scope: trace specific methods, not all methods
- Avoid tracing high-frequency methods
Problem: Garbled output with special characters
Solution: Set encoding:
btrace -Dfile.encoding=UTF-8 <PID> script.javaNow that you have BTrace running, explore these resources:
- BTrace Tutorial - Progressive lessons covering all features
- Quick Reference - Annotation and API cheat sheet
- Sample Scripts - 50+ real-world examples
- Troubleshooting Guide - Solutions to common problems
- BTrace Wiki - Comprehensive user guide
- Start simple: Begin with basic method tracing before complex instrumentation
- Test locally: Verify scripts on test applications before production use
- Use samples: Browse the 50+ sample scripts for patterns
- Monitor overhead: Always measure BTrace's performance impact
- Keep scripts focused: One script per specific issue
- Version control: Save useful scripts for reuse
- Documentation Hub - Complete documentation map and learning paths
- Quick Reference - Annotation and API cheat sheet
- BTrace Tutorial - Progressive lessons covering all features
- Troubleshooting Guide - Solutions to common problems
- FAQ - Common questions and best practices
- Slack: btrace.slack.com
- Gitter: gitter.im/btraceio/btrace
- GitHub Issues: github.com/btraceio/btrace/issues
- Wiki: github.com/btraceio/btrace/wiki