docs/FAQ.md
BTrace is a safe, dynamic tracing framework for the Java platform. It allows you to dynamically instrument running Java applications to inject tracing code without stopping, recompiling, or modifying the application.
Yes, with proper precautions:
@Sampled to reduce overhead@Level to enable/disable instrumentation dynamicallyNot recommended in production:
-u flag)| Aspect | BTrace | Traditional Logging |
|---|---|---|
| Code changes | None required | Requires code modification |
| Deployment | Dynamic attachment | Requires redeployment |
| Overhead when disabled | Zero | Some (log statements still evaluated) |
| Production use | Can add/remove anytime | Fixed at compile time |
| Flexibility | Change what to log without restart | Fixed logging points |
| Learning curve | Steeper | Simpler |
BTrace works with:
Limitations:
Impact depends on:
Typical overhead:
Best practices to minimize overhead:
// Use sampling
@Sampled(kind = Sampled.Sampler.Adaptive)
// Use level control
@OnMethod(enableAt = @Level(">=1"))
// Aggregate instead of printing each event
@OnTimer(5000)
public static void printSummary() { }
Yes. BTrace works well with Spring Boot:
// Trace Spring controller methods
@OnMethod(clazz = "@org.springframework.web.bind.annotation.RestController",
method = "@org.springframework.web.bind.annotation.GetMapping")
// Trace Spring services
@OnMethod(clazz = "@org.springframework.stereotype.Service", method = "/.*/")
// Trace specific beans
@OnMethod(clazz = "com.example.MyService", method = "process")
For containerized Spring Boot:
docker exec -it <container> btrace <PID> script.java
Specify the fully qualified class name:
// Trace JDBC
@OnMethod(clazz = "java.sql.Statement", method = "execute.*")
// Trace HTTP clients
@OnMethod(clazz = "org.apache.http.impl.client.CloseableHttpClient",
method = "execute")
// Trace logging frameworks
@OnMethod(clazz = "org.slf4j.impl.Log4jLoggerAdapter", method = "error")
No, BTrace is read-only. You can:
You cannot:
For runtime behavior modification, consider:
// Script with property
import org.openjdk.btrace.core.annotations.*;
@BTrace
public class MyTrace {
@Property
public static String filterValue = "default";
@OnMethod(...)
public static void handler(String arg) {
if (arg.equals(filterValue)) {
println("Matched: " + arg);
}
}
}
Pass via command line:
btrace <PID> MyTrace.java filterValue=custom
Or via agent:
java -javaagent:btrace-agent.jar=script=MyTrace.class,filterValue=custom MyApp
Yes, BTrace works well in microservice architectures:
Per-service tracing:
# Trace specific service
kubectl exec -it pod-name -- btrace <PID> script.java
Common use cases:
Considerations:
Use <init> for instance constructors, <clinit> for static initializers:
// Instance constructor
@OnMethod(clazz = "com.example.MyClass", method = "<init>")
public static void onNew() {
println("Object created");
}
// Static initializer
@OnMethod(clazz = "com.example.MyClass", method = "<clinit>")
public static void onStaticInit() {
println("Class initialized");
}
Yes, redirect output:
# Redirect to file
btrace <PID> script.java > output.txt
# Or use -o flag
btrace -o output.txt <PID> script.java
For programmatic file writing (requires unsafe mode):
btrace -u <PID> script.java
import java.io.*;
@BTrace(unsafe = true)
public class UnsafeFileWriter {
private static FileWriter fw;
@OnMethod(...)
public static void handler() throws Exception {
if (fw == null) {
fw = new FileWriter("/tmp/trace.log");
}
fw.write("Event\n");
fw.flush();
}
}
Ideal use cases:
Not ideal for:
Use @Injected without parameters for all services/extensions. The invokedynamic injector
detects how to construct injected services and extensions. If an extension requires runtime
context, it is initialized via Extension.initialize(ExtensionContext).
See also: Architecture → architecture/ExtensionInvokeDynamicBridge.md and
Extension Development Guide → btrace-extension-development-guide.md.
1. Tracing everything
// BAD
@OnMethod(clazz = "/.*/", method = "/.*/")
public static void traceAll() { }
2. Expensive operations in handlers
// BAD
@OnMethod(...)
public static void handler() {
for (int i = 0; i < 1000000; i++) { // Expensive loop
// ...
}
}
3. No sampling on hot paths
// BAD - called millions of times
@OnMethod(clazz = "com.example.HotClass", method = "hotMethod")
public static void handler() { }
// GOOD
@Sampled(kind = Sampled.Sampler.Adaptive)
@OnMethod(clazz = "com.example.HotClass", method = "hotMethod")
public static void handler() { }
4. Printing large objects
// BAD - object might be huge
@OnMethod(...)
public static void handler(Object obj) {
println(str(obj)); // Could be megabytes
}
// GOOD
@OnMethod(...)
public static void handler(Object obj) {
println(name(classOf(obj)) + "@" + str(identityHashCode(obj)));
}
5. Forgetting to detach
exitDirectory structure:
btrace-scripts/
├── common/
│ ├── MethodTiming.java
│ └── ExceptionTracker.java
├── database/
│ ├── SQLLogger.java
│ └── ConnectionPoolMonitor.java
├── http/
│ ├── RequestLogger.java
│ └── ResponseTimeTracker.java
└── memory/
└── AllocationTracker.java
Naming convention:
HttpRequestTracker not Script1SlowQueryDetector not DatabaseTraceSpringControllerTimerScript template:
/*
* Purpose: [What this script does]
* Target: [Which application/class]
* Usage: btrace <PID> ScriptName.java
* Author: [Your name]
* Date: [Creation date]
*/
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class ScriptName {
// Script implementation
}
| Feature | BTrace | JFR |
|---|---|---|
| Custom instrumentation | Yes | Limited (custom events) |
| Production overhead | Variable (1-50%) | Very low (<1%) |
| Learning curve | Moderate | Low |
| Built into JDK | No | Yes (Java 11+) |
| Dynamic attachment | Yes | Yes |
| Historical data | No | Yes (continuous recording) |
| GUI tools | Limited | Mission Control |
When to use BTrace over JFR:
When to use JFR over BTrace:
| Feature | BTrace | AspectJ |
|---|---|---|
| Runtime attachment | Yes | No (requires weaving) |
| Code modification | No | Yes |
| Deployment | Dynamic | Compile-time or load-time |
| Safety checks | Yes (enforced) | No |
| Complexity | Low-Medium | High |
| Use in production | Yes (safe mode) | Yes |
Use BTrace when:
Use AspectJ when:
| Feature | BTrace | Byteman |
|---|---|---|
| Primary purpose | Tracing/monitoring | Testing/fault injection |
| Safety | Enforced (safe mode) | Optional |
| Script language | Java | Rule-based DSL |
| Behavior modification | No | Yes |
| Learning curve | Medium | Medium-High |
Use BTrace when:
Use Byteman when:
See Troubleshooting Guide for detailed solutions.
Quick checklist:
btrace -v?// Add debug output
@OnMethod(...)
public static void handler() {
println("Handler called!");
println("Thread: " + threadName());
println("Stack:");
jstack(3);
}
// Start simple, add complexity
@OnTimer(1000)
public static void heartbeat() {
println("Script alive at " + timestamp());
}
Yes, BTrace supports Java 8-20. For Java 9+ you may need to add module opens:
btrace --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
<PID> script.java
Or add to target JVM startup options.
Yes, use @Export to expose metrics via JMX:
@BTrace
public class MetricsExporter {
@Export
private static long requestCount;
@Export
private static long errorCount;
@OnMethod(clazz = "com.example.Service", method = "handleRequest")
public static void onRequest() {
requestCount++;
}
@OnMethod(clazz = "com.example.Service", method = "handleRequest",
location = @Location(Kind.ERROR))
public static void onError() {
errorCount++;
}
}
Then access via JMX or integrate with monitoring tools.
BTrace has first-class support for Java Flight Recorder (JFR), allowing you to create custom JFR events with <1% overhead.
Example:
import org.openjdk.btrace.core.jfr.JfrEvent;
@BTrace
public class JfrIntegration {
@Event(
name = "MethodExecution",
label = "Method Execution Event",
category = {"myapp", "performance"},
fields = {
@Event.Field(type = Event.FieldType.STRING, name = "method"),
@Event.Field(type = Event.FieldType.LONG, name = "duration")
}
)
private static JfrEvent.Factory execEvent;
@TLS private static long startTime;
@OnMethod(clazz = "com.example.Service", method = "process")
public static void onEntry() {
startTime = timeNanos();
}
@OnMethod(clazz = "com.example.Service", method = "process",
location = @Location(Kind.RETURN))
public static void onReturn(@ProbeMethodName String method) {
JfrEvent event = Jfr.prepareEvent(execEvent);
if (Jfr.shouldCommit(event)) {
Jfr.setEventField(event, "method", method);
Jfr.setEventField(event, "duration", timeNanos() - startTime);
Jfr.commit(event);
}
}
}
See Getting Started: JFR Integration for complete guide.
Use JFR events when:
Use println when:
Performance comparison:
JFR events: <1% overhead
println: 1-50% overhead (depends on frequency)
Aggregations: ~1-5% overhead
JFR events are significantly faster because:
shouldCommit() allows JVM-level filteringBenchmark results (1M events):
Yes! BTrace JFR events appear alongside standard JVM events in Mission Control:
Steps:
jcmd <PID> JFR.start name=my-recording
jcmd <PID> JFR.dump name=my-recording filename=recording.jfr
recording.jfr in JDK Mission ControlYour BTrace events will have the category you specified (e.g., "myapp") and all defined fields.
Supported:
Not supported:
BTrace gracefully degrades on Java 9-10: JFR event methods return empty events that are silently ignored.
Version detection:
// BTrace automatically handles version differences
// No code changes needed for different Java versions
Yes, but with important considerations due to JEP 451 (introduced in JDK 21):
Current behavior (JDK 21+):
WARNING: A Java agent has been loaded dynamically
-XX:+EnableDynamicAgentLoading flagSolution for JDK 21+:
# Start your application with this flag to suppress warnings
java -XX:+EnableDynamicAgentLoading -jar your-application.jar
Future behavior:
In a future JDK release, dynamic agent loading will be disabled by default. The -XX:+EnableDynamicAgentLoading flag will be required to use BTrace's attach mode.
Alternatives:
java -javaagent:/path/to/btrace-agent.jar=script=YourScript.class -jar app.jar
-XX:+EnableDynamicAgentLoading to your JVM startup scripts for future compatibilityFor details, see JEP 451: Prepare to Disallow the Dynamic Loading of Agents and Troubleshooting: JVM Attachment Issues.
Basic pattern:
# Find pod and process
kubectl get pods
kubectl exec <pod-name> -- jps
# Run BTrace
kubectl exec -it <pod-name> -- btrace <PID> script.java
Copy script to pod:
kubectl cp MyTrace.java <pod-name>:/tmp/
kubectl exec -it <pod-name> -- btrace <PID> /tmp/MyTrace.java
Trace multiple pods:
for POD in $(kubectl get pods -l app=myapp -o name | cut -d/ -f2); do
kubectl exec $POD -- btrace 1 script.java &
done
Prerequisites:
shareProcessNamespace: true for sidecar patternSee Getting Started: Kubernetes and Troubleshooting: Kubernetes for comprehensive guides.
Yes, using batch scripts or parallel execution:
Shell script approach:
#!/bin/bash
DEPLOYMENT=$1
SCRIPT=$2
PODS=$(kubectl get pods -l app=$DEPLOYMENT -o jsonpath='{.items[*].metadata.name}')
for POD in $PODS; do
echo "Tracing $POD..."
kubectl exec $POD -- btrace 1 $SCRIPT > $POD.log 2>&1 &
done
wait
echo "All traces complete"
ConfigMap pattern:
apiVersion: v1
kind: ConfigMap
metadata:
name: btrace-scripts
data:
trace.java: |
@BTrace
public class Trace {
// script content
}
Then mount and use across all pods. See Troubleshooting: Batch Tracing.
Sidecar Container (Recommended for persistent tracing):
spec:
shareProcessNamespace: true
containers:
- name: app
image: myapp:latest
- name: btrace
image: bellsoft/liberica-openjdk-debian:11-cds
command: ["/bin/sh", "-c", "sleep infinity"]
Pros: Always available, can attach/detach anytime Cons: Extra resource usage Use when: Need on-demand tracing capability
Init Container (For startup tracing):
spec:
initContainers:
- name: setup-btrace
image: btrace:latest
command: ["cp", "-r", "/opt/btrace", "/shared"]
volumeMounts:
- name: shared
mountPath: /shared
Pros: No runtime overhead Cons: Only for startup instrumentation Use when: Debugging initialization issues
Agent Mode (For permanent instrumentation):
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/opt/btrace-agent.jar=script=/scripts/trace.class"
Pros: Active from process start Cons: Requires pod restart to change Use when: Need continuous tracing
BTrace doesn't have direct Prometheus integration, but you can use StatSD as a bridge:
Option 1: StatSD → Prometheus
@BTrace
public class MetricsExporter {
@OnMethod(clazz = "com.example.Service", method = "handleRequest")
public static void onRequest() {
// Send to StatSD (configure with -statsd flag)
Statsd.increment("requests.total");
}
@OnMethod(clazz = "com.example.Service", method = "handleRequest",
location = @Location(Kind.ERROR))
public static void onError() {
Statsd.increment("requests.errors");
}
}
Run with:
btrace -statsd statsd-exporter:8125 <PID> MetricsExporter.java
Then use statsd_exporter to export to Prometheus.
Option 2: JMX → Prometheus
@BTrace
public class JmxExporter {
@Export
private static long requestCount;
@OnMethod(...)
public static void handler() {
requestCount++;
}
}
Use jmx_exporter to scrape JMX metrics.
Recommended: For permanent monitoring, use APM tools (New Relic, DataDog) instead. BTrace is best for ad-hoc debugging.
Yes, BTrace works with service meshes without interference:
Why it works:
Considerations:
Resource limits: BTrace overhead may trigger CPU/memory limits
resources:
limits:
cpu: "500m" # Increase if needed
memory: "512Mi"
mTLS: Doesn't affect BTrace (uses local communication)
Port conflicts: BTrace port 2020 not intercepted by mesh
Multi-container pods: Use shareProcessNamespace: true for sidecar pattern
Service mesh telemetry and BTrace serve different purposes:
Use both together for comprehensive observability.
See Contributing Guide for details.
btrace-dist/src/main/resources/samples/ (50+ examples)BTrace is licensed under GPLv2 with the Classpath Exception. See LICENSE for details.