BTrace extensions provide reusable services that can be injected into BTrace scripts. This guide covers the recommended, plugin-based workflow using a single Gradle module with two source sets (api, impl). The plugin separates artifacts, generates metadata, shades implementation dependencies, and prepares distributables.
For API authoring rules that the build verifies, see docs/ExtensionInterfaceRules.md.
Extensions are isolated while exposing only their API to scripts:
Bootstrap ClassLoader
├── JRE classes
├── btrace-boot.jar (BTrace core + extension APIs)
└── Extension ClassLoaders (isolated)
├── Extension 1 (e.g., btrace-metrics)
├── Extension 2 (e.g., btrace-statsd)
└── Extension N (your extension)
Script ClassLoader (parent = null)
├── Script classes
└── Accesses extensions via invokedynamic bridge
Use a single Gradle module with two source sets:
your-extension/
├── build.gradle
└── src/
├── api/java/... (public API visible to scripts; JDK-only deps)
├── api/resources/...
├── impl/java/... (implementation; can use external libraries)
└── impl/resources/...
- API types are resolved by scripts (end up on bootstrap).
- Impl is isolated behind an extension classloader with shaded deps.
- The plugin produces an API JAR, a shadowed Impl JAR, and a distributable ZIP.
Apply the BTrace Gradle Extension Plugin and configure your extension via btraceExtension:
plugins {
id("org.openjdk.btrace.extension") version "<btraceVersion>"
}
repositories { mavenCentral() }
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
// Keep API free of external library types
// Put all runtime libs under Impl (the plugin will shade them)
}
btraceExtension {
id = "org.example.myext" // required: globally unique extension ID
name = "My Extension" // optional
description = "Does useful things" // optional
// Service interfaces that can be injected into scripts
// Auto-detected from @ServiceDescriptor, or declare explicitly:
services = [ "org.example.myext.api.MyService" ]
// Shade Impl dependencies to avoid conflicts
shadedPackages = [
"com.example.dep" : "org.example.myext.shaded.dep"
]
// Permissions
scanPermissions = true // default: infer from Impl bytecode + classpath
requiredPermissions = [ ] // optional additions/overrides
// Optional: other extension IDs you depend on
requiresExtensions = [ ]
}Alternative to the DSL:
- You can document extension details via the
@ExtensionDescriptorannotation in your API package’spackage-info.java. - Annotation source:
btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ExtensionDescriptor.java. - Fields:
name,version,description,minBTraceVersion,dependencies,permissions. - The
btraceExtensionblock remains the canonical source for manifest values;@ExtensionDescriptormainly assists tooling and validates that declaredpermissionsare covered by scanning orrequiredPermissions.
Outputs produced by the plugin:
- API JAR:
build/libs/<name>-<version>-api.jar(manifest + properties with extension metadata) - Impl JAR:
build/libs/<name>-<version>-impl.jar(shadowed/minimized, isolated at runtime) - Distribution ZIP:
build/distributions/<name>-<version>-extension.zip(bundles API + Impl)
Advanced (optional) knobs in btraceExtension:
autoApplyShadow(default true): auto-apply Shadow plugin if not applied.nullableAnnotations/nonnullAnnotations: additional nullability annotations (FQCN) for API linting.nullabilitySeverity(off|warn|error): nullability lint severity.shimabilitySeverity(warn|error): shim-compatibility lint severity.apiCtorSeverity(off|warn|error): flag public constructors in API classes.generateShimsReachableOnly(default true): generate shims only for interfaces reachable from declared services.
Define injectable service interfaces. Use the descriptors to help discovery and permission modeling.
package org.example.myext.api;
import org.openjdk.btrace.core.extensions.Permission;
import org.openjdk.btrace.core.extensions.ServiceDescriptor;
@ServiceDescriptor(permissions = { Permission.THREADS })
public interface MyService {
MyMetric metric(String name);
}Keep API signatures to JDK and your own API types; avoid external library types.
Provide concrete implementations and extend Extension to access the runtime context when needed.
package org.example.myext.impl;
import org.openjdk.btrace.core.extensions.Extension;
import org.example.myext.api.MyService;
public final class MyServiceImpl extends Extension implements MyService {
public MyServiceImpl() {}
// implement API methods...
}The plugin shades external libraries present in Impl according to shadedPackages.
package btrace;
import org.openjdk.btrace.core.annotations.BTrace;
import org.openjdk.btrace.core.annotations.Injected;
import org.openjdk.btrace.core.annotations.OnMethod;
import org.example.myext.api.MyService;
@BTrace
public class MyProbe {
@Injected
private static MyService svc;
@OnMethod(clazz = "com.example.App", method = "doWork")
public static void onDoWork() {
svc.metric("work");
}
}The plugin writes extension metadata into the API JAR manifest and a dedicated properties file; manual manifest editing is not needed. Key attributes include:
BTrace-Extension-Id,BTrace-Extension-Name,BTrace-Extension-DescriptionBTrace-Extension-Services(service interfaces)BTrace-Extension-Permissions(merged from scan + explicitrequiredPermissions)BTrace-Extension-Requires(dependent extension IDs)BTrace-Extension-Impl(Impl artifact coordinates/path)BTrace-Shaded-Packages(diagnostic relocations)
Permission configuration:
btraceExtension {
// Disable inference and declare explicitly (optional)
// scanPermissions = false
requiredPermissions = [ "NETWORK", "THREADS" ]
}At runtime, the agent consults this metadata to validate and enforce permissions.
- Keep the API free of external library types; prefer JDK and your API classes.
- Put all runtime libraries in Impl; use
shadedPackagesto relocate and avoid conflicts. - Do not include BTrace modules in your Impl artifact; only external libs are shaded.
Build artifacts:
- API JAR:
build/libs/<name>-<version>-api.jar - Impl JAR:
build/libs/<name>-<version>-impl.jar - ZIP:
build/distributions/<name>-<version>-extension.zip
Install by copying the ZIP contents (API + Impl) into an extensions directory:
# System-wide
unzip your-extension-<version>-extension.zip -d "$BTRACE_HOME/extensions/"
# User-specific
mkdir -p "$HOME/.btrace/extensions"
unzip your-extension-<version>-extension.zip -d "$HOME/.btrace/extensions/"Discovery locations:
$BTRACE_HOME/extensions/*.jar~/.btrace/extensions/*.jar
Configuration: $BTRACE_HOME/conf/extensions.conf
autoload = true
repositories = [ "${btrace.home}/extensions", "${user.home}/.btrace/extensions" ]- Unit test Impl logic normally (JUnit 5).
- Integration test with real BTrace scripts in
integration-tests. - Verify on supported JDKs (8, 11, 17+).
- Single module using
src/apiandsrc/impl - Apply
org.openjdk.btrace.extensionplugin - Set
btraceExtension.id, declare/annotateservices - Configure
shadedPackages; optionally tunerequiredPermissions - Keep API clean (JDK-only) and small
- Build and install ZIP into extensions dir
- Unit + integration tests pass on supported JDKs
- Zero-allocation hot paths; avoid boxing.
- Prefer lock-free primitives where possible.
- Lazy init; create objects only when needed.
- Ensure thread-safety; services can be called concurrently.
- Immutable snapshots for queries.
- Clear, minimal public surface.
- Use builders/factories exposed from the service for configuration objects.
// API
@ServiceDescriptor
public interface MetricsService {
HistogramConfigBuilder newHistogramConfig();
HistogramMetric histogram(String name, HistogramConfig cfg);
}
public interface HistogramConfig {}
public interface HistogramConfigBuilder {
HistogramConfigBuilder lowestDiscernibleValue(long v);
HistogramConfigBuilder highestTrackableValue(long v);
HistogramConfigBuilder significantDigits(int d);
HistogramConfig build();
}
// Probe (no `new` in scripts)
@BTrace
class HistoProbe {
@Injected static MetricsService metrics;
}- Script references a type not present in API → move it to
src/api/javaas an interface.
- Impl does not fully implement the API → keep API and Impl in lockstep.
- Missing/incorrect metadata → ensure
btraceExtension.id/servicesare set or APIs are annotated.
- Missing relocations → add entries under
shadedPackagesfor third-party libraries.
Use a single module with api and impl source sets and the BTrace extension plugin to produce clean, isolated, and self-describing extensions. The plugin handles artifact separation, metadata, permissions, shading, and packaging, so you can focus on a stable API and solid implementation.