Java Custom Instrumentation
This guide explains how to implement Custom Instrumentation in Java applications using the Motadata Custom Instrumentation package.
Custom instrumentation helps you add business-specific attributes to the currently active span without affecting the existing zero-code or auto-instrumentation flow. It is designed to align with the OTel instrumentation model while keeping attribute handling safe, validated, and consistent.
Prerequisites
- The application must already be instrumented using Motadata Auto Instrumentation so that active span context is available.
- Java 8 or higher must be used.
- The Motadata Agent 8.1.0 or higher must be installed and running.
- The application must already be generating traces in APM.
- Maven or Gradle must be available based on your project build system.
Configuration Steps
Step 1: Install the Java Custom Instrumentation Package
Add the Motadata Custom Instrumentation package to your Java application.
Maven
Add the following repository and dependency to your pom.xml:
<repositories>
<repository>
<id>github</id>
<name>GitHub Motadata Apache Maven Packages</name>
<url>https://maven.pkg.github.com/motadata2025/motadata-apm-custom-instrumentation-java</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>motadata-apm</groupId>
<artifactId>custom-instrumentation</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
Add GitHub Packages credentials to ~/.m2/settings.xml using a Personal Access Token with read:packages permission:
<settings>
<servers>
<server>
<id>github</id>
<username>YOUR_GITHUB_USERNAME</username>
<password>YOUR_GITHUB_PAT</password>
</server>
</servers>
</settings>
- Create a GitHub Personal Access Token with
read:packagespermission. - Replace
YOUR_GITHUB_USERNAMEandYOUR_GITHUB_PAT. - Keep
<id>github</id>the same as the Maven repository definition.
Gradle
Add the following configuration to your build.gradle:
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/motadata2025/motadata-apm-custom-instrumentation-java")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
}
dependencies {
implementation "motadata-apm:custom-instrumentation:1.0.0"
}
Add credentials in ~/.gradle/gradle.properties:
gpr.user=YOUR_GITHUB_USERNAME
gpr.key=YOUR_GITHUB_PAT
- Use the same GitHub Personal Access Token with
read:packages. - Keep the keys as
gpr.userandgpr.keyto match the Gradle configuration.
Step 2: Import the Package
After adding the dependency, import the package in your Java code:
import motadata.apm.CustomInstrumentation;
Step 3: Add Custom Attributes to the Active Span
Use the package APIs to inject custom attributes into the currently active span.
Basic Example
try {
CustomInstrumentation.set("apm.user.id", 12345L);
CustomInstrumentation.set("apm.user.name", "john.doe");
CustomInstrumentation.set("apm.request.success", true);
CustomInstrumentation.setStringList("apm.tags", Arrays.asList("api", "production", "critical"));
} catch (Exception exception) {
System.err.println("Failed to set custom attributes: " + exception.getMessage());
}
Keys are automatically prefixed with apm. if the prefix is not provided. However, it is recommended to explicitly use the apm. prefix for consistency across services.
All setter methods throw Exception when the input is invalid or when no active span is available. For that reason, wrap all custom instrumentation calls in a try/catch block so business execution is not impacted.
Supported Methods
Scalar Attribute Setters
| Method | Parameter |
|---|---|
set(String key, Boolean value) | Boolean |
set(String key, Double value) | Double |
set(String key, Integer value) | Integer |
set(String key, Long value) | Long |
set(String key, String value) | String |
Collection Attribute Setters
| Method | Parameter |
|---|---|
setBooleanList(String key, List<Boolean> values) | List<Boolean> |
setDoubleList(String key, List<Double> values) | List<Double> |
setIntegerList(String key, List<Integer> values) | List<Integer> |
setLongList(String key, List<Long> values) | List<Long> |
setStringList(String key, List<String> values) | List<String> |
Attribute Behavior and Validation Rules
The Java Custom Instrumentation package validates both keys and values before attaching attributes to the span.
Key Handling
- Keys are trimmed before validation.
- Keys are converted to lowercase.
- Keys are automatically prefixed with
apm.if not already present. - Keys must contain only alphanumeric characters and dots.
- Keys with whitespace or unsupported symbols are rejected.
- Keys cannot be null or empty.
Value Handling
- Null scalar values are rejected.
- Empty string values are ignored and are not added to the trace.
- Double values must be finite.
NaNandInfinityare rejected. - Integer values are internally stored as
longfor compatibility. - Null values are removed from lists automatically.
- Lists must contain at least one non-null value after filtering.
Runtime Behavior
- The package is thread-safe and can be used safely in concurrent JVM execution.
- An
Exceptionis thrown if there is no active span. - An
Exceptionis thrown when the input is invalid.
Example Scenarios
Add Business Context to Request Flow
try {
CustomInstrumentation.set("apm.order.id", 98765L);
CustomInstrumentation.set("apm.order.channel", "web");
CustomInstrumentation.set("apm.order.priority", "high");
} catch (Exception exception) {
System.err.println("Failed to set order attributes: " + exception.getMessage());
}
Add Collection Attributes
try {
CustomInstrumentation.setStringList(
"apm.order.items",
Arrays.asList("router", "switch", "firewall")
);
} catch (Exception exception) {
System.err.println("Failed to set order items: " + exception.getMessage());
}
Add Conditional Attributes
try {
if (paymentAmount > 10000) {
CustomInstrumentation.set("apm.payment.high_value", true);
}
} catch (Exception exception) {
System.err.println("Failed to set payment attributes: " + exception.getMessage());
}
Add Error Context
try {
refundService.processRefund(refundId);
} catch (Exception exception) {
try {
CustomInstrumentation.set("apm.refund.status", "failed");
CustomInstrumentation.set("apm.refund.error_message", exception.getMessage());
} catch (Exception instrumentationException) {
System.err.println("Failed to set refund attributes: " + instrumentationException.getMessage());
}
throw exception;
}
Advanced Example
The following example demonstrates enterprise-style usage where custom attributes are added across the transaction lifecycle.
import motadata.apm.CustomInstrumentation;
import java.util.Arrays;
public class OrderService {
public void processOrder(Order order) {
try {
CustomInstrumentation.set("apm.order.id", order.getId());
CustomInstrumentation.set("apm.order.customer", order.getCustomerName());
CustomInstrumentation.set("apm.order.amount", order.getAmount());
CustomInstrumentation.set("apm.order.currency", order.getCurrency());
CustomInstrumentation.setStringList(
"apm.order.tags",
Arrays.asList("sales", "priority", "online")
);
if (order.getAmount() > 10000) {
CustomInstrumentation.set("apm.order.high_value", true);
}
validateOrder(order);
createInvoice(order);
dispatchOrder(order);
CustomInstrumentation.set("apm.order.status", "completed");
} catch (Exception exception) {
try {
CustomInstrumentation.set("apm.order.status", "failed");
CustomInstrumentation.set("apm.order.error_message", exception.getMessage());
} catch (Exception instrumentationException) {
System.err.println("Failed to set failure attributes: " + instrumentationException.getMessage());
}
throw new RuntimeException(exception);
}
}
private void validateOrder(Order order) throws Exception {
CustomInstrumentation.set("apm.order.stage", "validation");
}
private void createInvoice(Order order) throws Exception {
CustomInstrumentation.set("apm.order.stage", "invoice_creation");
}
private void dispatchOrder(Order order) throws Exception {
CustomInstrumentation.set("apm.order.stage", "dispatch");
}
}
This approach helps correlate business metadata such as order ID, customer, stage, priority, and failure reason directly with the technical trace.
Best Practices
- Use descriptive and hierarchical keys such as
apm.order.idorapm.customer.tier. - Prefer adding the
apm.prefix explicitly. - Use the correct value type for the attribute being added.
- Wrap custom instrumentation calls in
try/catch. - Keep attribute naming consistent across applications and services.
- Use list setters when working with collections.
- Add only meaningful business attributes that improve trace analysis.
What to Avoid
- Do not inject sensitive data such as passwords, secrets, or tokens.
- Do not use inconsistent naming for the same business concept.
- Do not pass invalid keys containing spaces or unsupported characters.
- Do not add empty string value, as it will not appear in traces.
- Do not assume custom attributes will be added when no active span is available.
Verify the Attributes
After implementing custom instrumentation and redeploying the application:
- Go to APM Explorer.
- Open the require Service.
- Open the required trace.
- Select the relevant span.
- Confirm that the custom attributes are visible in span attributes.
Summary
The Motadata Java Custom Instrumentation package allows you to add validated, namespaced, business-specific attributes to the currently active span using a safe and consistent API model. It works alongside existing auto-instrumentation and helps enrich traces with operational context that is meaningful for real-world analysis.