Skip to main content

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:packages permission.
  • Replace YOUR_GITHUB_USERNAME and YOUR_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.user and gpr.key to 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

MethodParameter
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

MethodParameter
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. NaN and Infinity are rejected.
  • Integer values are internally stored as long for 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 Exception is thrown if there is no active span.
  • An Exception is 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.id or apm.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:

  1. Go to APM Explorer.
  2. Open the require Service.
  3. Open the required trace.
  4. Select the relevant span.
  5. 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.