Backend Setup: Java
This guide walks you through instrumenting a Java application with the SF Veritas SDK for Sailfish Enterprise. Java instrumentation captures logs, print statements, exceptions, HTTP request/response telemetry, and function execution spans.
If you connected GitHub and received Auto-Installation PRs, the API key, service identifier, and graphql endpoint are already configured for you. Merge the PR and you're done.
Getting Your API Key
- Open the Sailfish dashboard
- Log in with your enterprise email
- Navigate to Settings > Configuration
- Copy your company's API key
Installation
Maven
Add the Sailfish repository and SDK dependency to your pom.xml:
<repositories>
<repository>
<id>sailfish</id>
<url>https://us-central1-maven.pkg.dev/sailfish-ai/sailfish-maven</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>ai.sailfish</groupId>
<artifactId>sf-veritas</artifactId>
<version>0.1.0</version>
</dependency>
<!-- Optional: automatic function instrumentation -->
<dependency>
<groupId>ai.sailfish</groupId>
<artifactId>sf-veritas-agent</artifactId>
<version>0.1.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Gradle (Groovy)
repositories {
maven {
url "https://us-central1-maven.pkg.dev/sailfish-ai/sailfish-maven"
}
}
dependencies {
implementation 'ai.sailfish:sf-veritas:0.1.0'
runtimeOnly 'ai.sailfish:sf-veritas-agent:0.1.0' // Optional
}
Gradle (Kotlin DSL)
repositories {
maven {
url = uri("https://us-central1-maven.pkg.dev/sailfish-ai/sailfish-maven")
}
}
dependencies {
implementation("ai.sailfish:sf-veritas:0.1.0")
runtimeOnly("ai.sailfish:sf-veritas-agent:0.1.0") // Optional
}
Basic Setup
Add the following to your application's startup code:
import com.sailfish.veritas.Sailfish;
import com.sailfish.veritas.SailfishConfig;
public class Application {
public static void main(String[] args) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<see-api-key-from-your-account-settings-page>")
.serviceIdentifier("acme-corp/java-api/src/main/java/com/acme/Application.java") // Format: <org>/<repo>/<path-to-this-file>
.serviceVersion("1.0.0")
.build());
// Start your application...
}
}
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
apiKey | String | Yes | Your Sailfish Enterprise API key |
serviceIdentifier | String | Yes | Unique identifier in <org>/<repo>/<path> format |
serviceVersion | String | No | Version of your service |
serviceDisplayName | String | No | Display name in the UI |
gitSha | String | No | Git commit SHA |
gitOrg | String | No | Git organization |
gitRepo | String | No | Git repository name |
gitProvider | String | No | Git provider (e.g., "github") |
domainsToNotPropagateHeadersTo | List<String> | No | Domains to skip header propagation |
routesToSkipNetworkHops | List<String> | No | Routes to skip inbound tracing |
What Gets Captured Automatically
Once setupInterceptors is called, these are captured with zero additional code:
- Structured logs -- All SLF4J, Logback, Log4j2, JUL, and JBoss Logger calls
- Print statements -- All
System.out.println()andSystem.err.println()output - Exceptions -- Manually reported exceptions with
Sailfish.transmitException() - Outbound HTTP requests -- Via
java.net.http.HttpClientdefault transport - Inbound HTTP requests -- Via framework-specific filters (see below)
Framework Integration
Spring Boot
Register the SailfishSpringInterceptor via a WebMvcConfigurer:
import com.sailfish.veritas.network.inbound.SailfishSpringInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SailfishSpringInterceptor());
}
}
Initialize Sailfish in your @SpringBootApplication main class:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<see-api-key-from-your-account-settings-page>")
.serviceIdentifier("acme-corp/spring-api/src/main/java/com/acme/Application.java") // Format: <org>/<repo>/<path-to-this-file>
.build());
SpringApplication.run(Application.class, args);
}
}
Jakarta Servlet
For Jakarta Servlet 6.0+ containers (Jetty 12, Tomcat 10.1+), register the SailfishServletFilter.JakartaFilter:
import com.sailfish.veritas.network.inbound.SailfishServletFilter;
import jakarta.servlet.DispatcherType;
import java.util.EnumSet;
// Programmatic registration (embedded Jetty example)
FilterHolder sailfishFilter = new FilterHolder(new SailfishServletFilter.JakartaFilter());
context.addFilter(sailfishFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
For web.xml registration:
<filter>
<filter-name>SailfishFilter</filter-name>
<filter-class>com.sailfish.veritas.network.inbound.SailfishServletFilter$JakartaFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SailfishFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
JAX-RS (Jersey, RESTEasy)
Register SailfishJaxRsFilter.JakartaFilter in your JAX-RS Application:
import com.sailfish.veritas.network.inbound.SailfishJaxRsFilter;
import jakarta.ws.rs.core.Application;
import java.util.Set;
public class MyApp extends Application {
@Override
public Set<Class<?>> getClasses() {
return Set.of(
MyResource.class,
SailfishJaxRsFilter.JakartaFilter.class
);
}
}
Or with Jersey ResourceConfig:
ResourceConfig config = new ResourceConfig();
config.register(SailfishJaxRsFilter.JakartaFilter.class);
config.register(MyResource.class);
Quarkus
Quarkus uses Jakarta REST (RESTEasy Reactive). Create a @Provider class that extends the Sailfish filter:
import com.sailfish.veritas.network.inbound.SailfishJaxRsFilter;
import jakarta.ws.rs.ext.Provider;
@Provider
public class SailfishFilterProvider extends SailfishJaxRsFilter.JakartaFilter {
}
Initialize Sailfish on startup:
import com.sailfish.veritas.Sailfish;
import com.sailfish.veritas.SailfishConfig;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class SailfishInit {
void onStart(@Observes StartupEvent ev) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<see-api-key-from-your-account-settings-page>")
.serviceIdentifier("acme-corp/quarkus-api/src/main/java/com/acme/SailfishInit.java") // Format: <org>/<repo>/<path-to-this-file>
.build());
}
}
Micronaut
The SailfishMicronautFilter is auto-discovered by Micronaut when sf-veritas is on the classpath. It uses the @Filter("/**") annotation for automatic registration.
Initialize Sailfish with a @Context @Singleton bean:
import com.sailfish.veritas.Sailfish;
import com.sailfish.veritas.SailfishConfig;
import io.micronaut.context.annotation.Context;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Singleton;
@Context
@Singleton
public class SailfishInit {
@PostConstruct
void init() {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<see-api-key-from-your-account-settings-page>")
.serviceIdentifier("acme-corp/micronaut-api/src/main/java/com/acme/SailfishInit.java") // Format: <org>/<repo>/<path-to-this-file>
.build());
}
}
No additional filter registration is needed -- Micronaut discovers it automatically.
Vert.x
Register the SailfishVertxHandler as a route handler before your application routes:
import com.sailfish.veritas.network.inbound.SailfishVertxHandler;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);
// Body handler must be before Sailfish handler for request body capture
router.route().handler(BodyHandler.create());
// Register Sailfish handler for inbound HTTP tracing
router.route().handler(new SailfishVertxHandler());
// Your routes
router.get("/api/users").handler(ctx -> {
ctx.json(Map.of("users", List.of()));
});
vertx.createHttpServer()
.requestHandler(router)
.listen(8080);
Dropwizard
Dropwizard 4.x uses Jetty + Jersey (Jakarta namespace). Register the Sailfish JAX-RS filter in your Application's run() method:
import com.sailfish.veritas.network.inbound.SailfishJaxRsFilter;
import io.dropwizard.core.Application;
import io.dropwizard.core.setup.Environment;
public class MyApp extends Application<MyConfig> {
@Override
public void run(MyConfig config, Environment environment) {
// Register Sailfish filter
environment.jersey().register(new SailfishJaxRsFilter.JakartaFilter());
// Register your resources
environment.jersey().register(new MyResource());
}
}
Play Framework
Play Framework is not currently supported by the Sailfish Java SDK. Play uses sbt as its build system and has a custom classloading architecture that requires dedicated integration work. If you need Play support, please contact us at support@sailfishqa.com.
Automatic Function Instrumentation (ByteBuddy Agent)
For full function-level tracing in the Flamechart -- including automatic argument capture and return values -- use the sf-veritas-agent Java agent.
How It Works
The agent uses ByteBuddy to intercept method entry and exit at class load time. For every method in your configured packages, it:
- Method entry -- Records the method name, class, and captures all argument values
- Method exit -- Records timing and return value, ends the span
- Exception thrown -- Captures the exception and ends the span with error
Your source files are never modified. Instrumentation happens at class load time via the JVM agent mechanism.
Usage
java -javaagent:sf-veritas-agent-0.1.0.jar=packages=com.myapp -jar myapp.jar
Agent Arguments
| Argument | Required | Description |
|---|---|---|
packages | Yes | Semicolon-separated packages to instrument (e.g., com.myapp;com.shared) |
apiKey | No | API key (can also be set via SailfishConfig or env var) |
serviceIdentifier | No | Service identifier |
serviceVersion | No | Service version |
excludeMethods | No | Semicolon-separated method names to exclude |
excludeConstructors | No | Exclude constructors (default: true) |
excludeGettersSetters | No | Exclude getters/setters (default: true) |
@CaptureSpan Annotation
For selective instrumentation, annotate individual methods with @CaptureSpan:
import com.sailfish.veritas.span.CaptureSpan;
public class OrderService {
@CaptureSpan
public String processOrder(Order order) {
// Method execution is timed and profiled
return "processed";
}
@CaptureSpan(captureArgs = false, captureReturn = false)
public void sensitiveMethod(String secret) {
// Only timing is captured, no argument/return data
}
@CaptureSpan(name = "custom-span-name", sampleRate = 0.5)
public void highFrequencyMethod() {
// Custom name and 50% sampling
}
}
| Attribute | Type | Default | Description |
|---|---|---|---|
captureArgs | boolean | true | Capture method arguments |
captureReturn | boolean | true | Capture return value |
argLimitMB | double | -1 (global default) | Max argument serialization size in MB |
returnLimitMB | double | -1 (global default) | Max return value serialization size in MB |
name | String | "" (method name) | Custom span name |
sampleRate | double | -1 (global default) | Sampling rate (0.0-1.0) |
@CaptureSpan requires the ByteBuddy agent to be loaded. Without the agent, the annotation has no effect.
Manual Function Tracing
For fine-grained control over which functions are traced, use the SailfishSpan API:
Basic Span
import com.sailfish.veritas.span.SailfishSpan;
public String processOrder(String orderId) {
SailfishSpan span = SailfishSpan.start("processOrder");
try {
String result = doWork(orderId);
span.end(result);
return result;
} catch (Exception e) {
span.endWithError(e);
throw e;
}
}
Span with Arguments
SailfishSpan span = SailfishSpan.start("processOrder", Map.of(
"orderId", orderId,
"amount", amount
));
try {
// ... function body ...
span.end(result);
} catch (Exception e) {
span.endWithError(e);
throw e;
}
Exception Reporting
import com.sailfish.veritas.Sailfish;
try {
riskyOperation();
} catch (Exception e) {
Sailfish.transmitException(e);
// Handle or rethrow
}
User Identification
Sailfish.identify("user-123", Map.of(
"email", "user@example.com",
"name", "Jane Doe",
"plan", "enterprise"
));
GIT_SHA
The GIT_SHA environment variable allows Sailfish to correlate telemetry with specific commits.
The SDK auto-detects GIT_SHA from common CI/CD platforms:
| CI Platform | Environment Variable |
|---|---|
| GitHub Actions | GITHUB_SHA |
| GitLab CI | CI_COMMIT_SHA |
| CircleCI | CIRCLE_SHA1 |
| Jenkins | GIT_COMMIT |
| Bitbucket Pipelines | BITBUCKET_COMMIT |
| Travis CI | TRAVIS_COMMIT |
| Azure DevOps | BUILD_SOURCEVERSION |
| AWS CodeBuild | CODEBUILD_RESOLVED_SOURCE_VERSION |
| Vercel | VERCEL_GIT_COMMIT_SHA |
For custom CI systems, set it manually:
# Docker
docker build --build-arg GIT_SHA=$(git rev-parse HEAD) .
# In Dockerfile
ARG GIT_SHA
ENV GIT_SHA=$GIT_SHA
# Maven (pass as system property)
mvn package -Dgit.sha=$(git rev-parse HEAD)
# Gradle
./gradlew build -Pgit.sha=$(git rev-parse HEAD)
You can also pass it directly to the SDK:
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<see-api-key-from-your-account-settings-page>")
.gitSha(System.getenv("GIT_SHA"))
.serviceIdentifier("acme-corp/java-api/src/main/java/com/acme/Application.java") // Format: <org>/<repo>/<path-to-this-file>
.build());
Environment Variables
| Variable | Default | Description |
|---|---|---|
SF_DEBUG | false | Enable debug logging to stderr |
SF_LOG_IGNORE_REGEX | (health check pattern) | Regex to suppress matching log messages |
SF_FUNCSPAN_CAPTURE_ARGUMENTS | true | Capture function arguments |
SF_FUNCSPAN_CAPTURE_RETURN_VALUE | true | Capture return values |
SF_FUNCSPAN_ARG_LIMIT_MB | 1 | Max argument capture size in MB |
SF_FUNCSPAN_RETURN_LIMIT_MB | 1 | Max return value capture size in MB |
SF_FUNCSPAN_ENABLE_SAMPLING | false | Enable span sampling |
SF_FUNCSPAN_SAMPLE_RATE | 1.0 | Function span sampling rate (0.0 to 1.0) |
SF_FUNCSPAN_CAPTURE_SF_VERITAS | false | Capture spans for Sailfish SDK internals |
SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONS | true | Auto-capture all child function calls |
SF_NETWORKHOP_CAPTURE_ENABLED | true | Enable inbound/outbound HTTP tracing |
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS | false | Capture HTTP request headers |
SF_NETWORKHOP_CAPTURE_REQUEST_BODY | false | Capture HTTP request bodies |
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS | false | Capture HTTP response headers |
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY | false | Capture HTTP response bodies |
SF_NETWORKHOP_REQUEST_LIMIT_MB | 1 | Max request body capture size in MB |
SF_NETWORKHOP_RESPONSE_LIMIT_MB | 1 | Max response body capture size in MB |
SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES | -- | Comma-separated routes to skip inbound tracing |
SF_DISABLE_PRINT_CAPTURE | false | Disable stdout/stderr capture |
PRINT_CONFIGURATION_STATUSES | false | Print configuration status on startup |
Configuration File
Create a .sailfish file in your project root for per-file and per-function span configuration:
{
"files": {
"*.java": {
"capture_arguments": true,
"capture_return_value": true,
"sample_rate": 1.0
},
"*Test.java": {
"sample_rate": 0.0
}
},
"functions": {
"processOrder": {
"capture_arguments": true,
"capture_return_value": true,
"arg_limit_mb": 2,
"return_limit_mb": 2
},
"healthCheck": {
"sample_rate": 0.0
}
}
}
Docker Compose
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src/ src/
RUN mvn clean package -DskipTests -q
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /app/target/myapp.jar /app/app.jar
COPY --from=builder /app/target/dependency/sf-veritas-agent-0.1.0.jar /app/agent.jar
ARG GIT_SHA
ENV GIT_SHA=$GIT_SHA
ENTRYPOINT ["java", "-javaagent:/app/agent.jar=packages=com.myapp", "-jar", "app.jar"]
# docker-compose.yml
services:
api:
build:
context: .
args:
GIT_SHA: ${GIT_SHA:-$(git rev-parse HEAD)}
ports:
- "8080:8080"
Verifying the Setup
- Deploy your application with the Sailfish SDK configured
- Trigger some HTTP requests
- Open the Sailfish dashboard -- you should see telemetry appearing
Debug Mode
Enable debug output to verify instrumentation is working:
SF_DEBUG=true java -javaagent:sf-veritas-agent.jar=packages=com.myapp -jar myapp.jar
You'll see output like:
[Sailfish Veritas] Agent loaded -- instrumenting packages: [com.myapp]
[Sailfish] setupInterceptors() called
[Sailfish] Log capture initialized
[Sailfish] Print capture initialized
Troubleshooting
No logs appearing
- Check the API key: Ensure
apiKeyis set to your Enterprise API key - Check the service identifier: Ensure
serviceIdentifieruses the<org>/<repo>/<path>format - Check terminal output: Look for
[Sailfish]initialization messages
Agent not loading
- Verify the agent JAR path:
java -javaagent:/path/to/sf-veritas-agent-0.1.0.jar=packages=com.myapp - Check the
packagesargument -- it must match your application's package prefix - Enable debug:
SF_DEBUG=trueto see which classes are being instrumented
Connection errors
- Ensure your deployment can reach
https://api-service.sailfish.ai - Check that outbound HTTPS (port 443) is not blocked by a firewall or network policy
No function spans in Flamechart
- Ensure the ByteBuddy agent is loaded (check for
[Sailfish Veritas] Agent loadedin output) - Verify the
packagesargument includes your application package - Check
SF_FUNCSPAN_CAPTURE_ARGUMENTSandSF_FUNCSPAN_CAPTURE_RETURN_VALUEare not disabled
Multi-Service Setup
When running multiple Java services, give each a unique serviceIdentifier following the <org>/<repo>/<path> format:
// user-service
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<see-api-key-from-your-account-settings-page>")
.serviceIdentifier("acme-corp/user-service/src/main/java/com/acme/Application.java") // Format: <org>/<repo>/<path-to-this-file>
.build());
// order-service
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("<ApiKey />")
.serviceIdentifier("acme-corp/order-service/src/main/java/com/acme/Application.java") // Format: <org>/<repo>/<path-to-this-file>
.build());
Use the service filter in the Sailfish dashboard to switch between services.
Next Steps
- Set up your frontend application (optional)
- Check the Sailfish dashboard to verify telemetry is flowing
Looking to set up SF Veritas for local development with the Desktop App? See the Desktop App Java guide.