Tracing for Java
Java apps export traces to Railway with the OpenTelemetry Java agent. The agent attaches to the JVM at startup and instruments Spring, Jakarta servlets, JDBC, HTTP clients, and hundreds of other libraries without code changes.
Before you start, enable tracing for the service. Railway adds the OTEL_* variables on the next deploy, and the agent reads them.
Add the agent to your image
The agent is a single JAR file. Download it during the build so it's available at runtime. With a Dockerfile:
FROM eclipse-temurin:21-jre
WORKDIR /app
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar \
/app/opentelemetry-javaagent.jar
COPY build/libs/app.jar /app/app.jar
CMD ["java", "-javaagent:/app/opentelemetry-javaagent.jar", "-jar", "/app/app.jar"]Pin a release version in the URL rather than latest for reproducible builds. Without a Dockerfile, download the JAR in your build command with curl -L -o opentelemetry-javaagent.jar <url>, and reference it from the start command.
Set the start command
Pass the agent to the JVM with -javaagent:
java -javaagent:opentelemetry-javaagent.jar -jar app.jarOr leave the start command alone and set JAVA_TOOL_OPTIONS as a service variable, which the JVM reads at startup:
JAVA_TOOL_OPTIONS=-javaagent:/app/opentelemetry-javaagent.jarThe agent configures itself from environment variables. Besides the variables Railway provides, set these two on the service. The agent exports metrics and logs by default, and Railway's receiver accepts traces only:
OTEL_METRICS_EXPORTER=none
OTEL_LOGS_EXPORTER=noneSpring Boot
The agent instruments Spring MVC, Spring WebFlux, Spring Data, and the rest of the framework. Each incoming request produces a server span named after the method and the controller's route pattern, with http.route and http.response.status_code attributes. Calls made with RestTemplate, WebClient, or RestClient produce client spans and carry the trace context onward. JDBC queries produce client spans with the statement.
If you'd rather not run an agent, the OpenTelemetry Spring Boot starter adds the same instrumentation as a dependency and reads the same OTEL_* variables.
Add a custom span
Add the opentelemetry-api dependency and annotate a method with @WithSpan, which the agent turns into a span:
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
import io.opentelemetry.instrumentation.annotations.WithSpan;
public class CheckoutService {
@WithSpan("calculate-total")
public int calculateTotal(@SpanAttribute("cart.items") int itemCount, Cart cart) {
return priceItems(cart);
}
}The annotations come from the opentelemetry-instrumentation-annotations artifact. For spans that don't map to a method, use the Tracer from GlobalOpenTelemetry.getTracer("checkout") directly.
Verify
- Deploy the service and send it a few requests.
- Open the Traces page. In the Tracing setup panel, the App indicator for the service turns green when its first span arrives.
- Open a trace. The request's server span and any child spans appear under the edge and proxy spans.
If spans from the app don't arrive, see Troubleshooting. Set OTEL_JAVAAGENT_DEBUG=true on the service to have the agent log its configuration and export attempts. The agent adds a few seconds to JVM startup, so give the service a health check if it doesn't have one.