Backend Setup: Java
This guide walks you through instrumenting a Java application with the SF Veritas SDK. Java instrumentation captures logs, print statements, exceptions, HTTP request/response telemetry, and function execution spans.
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. Use an environment variable check so instrumentation only runs during local development:
import com.sailfish.veritas.Sailfish;
import com.sailfish.veritas.SailfishConfig;
public class Application {
public static void main(String[] args) {
// Initialize SF Veritas ONLY in development mode
if ("true".equalsIgnoreCase(System.getenv("SF_DEV"))) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("my-java-service")
.serviceVersion("1.0.0")
.build());
}
// Start your application...
}
}
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
apiKey | String | Yes | Use "sf-veritas-local" for local development |
graphqlEndpoint | String | Yes | URL of the local collector (default port 6776) |
serviceIdentifier | String | Yes | Unique name for your service |
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) {
if ("true".equalsIgnoreCase(System.getenv("SF_DEV"))) {
registry.addInterceptor(new SailfishSpringInterceptor());
}
}
}
Initialize Sailfish in your @SpringBootApplication main class:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
if ("true".equalsIgnoreCase(System.getenv("SF_DEV"))) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("my-spring-service")
.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) {
if ("true".equalsIgnoreCase(System.getenv("SF_DEV"))) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("my-quarkus-service")
.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() {
if ("true".equalsIgnoreCase(System.getenv("SF_DEV"))) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("my-micronaut-service")
.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) |
graphqlEndpoint | No | Custom endpoint URL |
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) |
Example with Maven
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:${settings.localRepository}/ai/sailfish/sf-veritas-agent/0.1.0/sf-veritas-agent-0.1.0.jar=packages=com.myapp</argLine>
</configuration>
</plugin>
@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"
));
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 |
SAILFISH_GRAPHQL_ENDPOINT | — | Alternative to SailfishConfig.graphqlEndpoint |
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
}
}
}
File Configuration
| Option | Type | Description |
|---|---|---|
capture_arguments | bool | Capture function arguments |
capture_return_value | bool | Capture return values |
arg_limit_mb | int | Max argument size in MB |
return_limit_mb | int | Max return value size in MB |
sample_rate | float | Sampling rate (0.0 to 1.0) |
Function Configuration
Same options as file configuration but applied to specific function names. Function-level config takes priority over file-level config.
Docker Compose
For local development with Docker Compose, include the agent JAR in your build and pass -javaagent as a JVM argument:
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
ENV SF_DEV=true
ENTRYPOINT ["java", "-javaagent:/app/agent.jar=packages=com.myapp", "-jar", "app.jar"]
# docker-compose.yml
services:
api:
build: .
ports:
- "8080:8080"
environment:
- SF_DEV=true
- SF_GRAPHQL_ENDPOINT=http://host.docker.internal:6776/graphql/
Use host.docker.internal to reach the SF Veritas Desktop App running on your host machine. This works on Docker Desktop for Mac and Windows. On Linux, add extra_hosts: ["host.docker.internal:host-gateway"] to your service.
Verifying the Setup
- Start your application with SF Veritas enabled
- Open the SF Veritas Desktop App
- Open the Console panel — you should see logs and print statements
- Trigger some HTTP requests — you should see them in the Network panel
- Open the Flamechart — you should see function execution traces (requires agent)
Debug Mode
Enable debug output to verify instrumentation is working:
SF_DEV=true 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 desktop app: Ensure the SF Veritas Desktop App is running
- Verify the endpoint: Ensure
graphqlEndpointmatches your server port - Check
SF_DEV: Must be set to"true" - 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 refused errors
- Verify the SF Veritas Desktop App is installed and running
- Check that the local server is running (look for server status in the Desktop App)
- Ensure port 6776 is not blocked by a firewall
- In Docker: use
host.docker.internalinstead oflocalhost
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 locally, give each a unique serviceIdentifier:
// user-service
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("user-service")
.build());
// order-service
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("order-service")
.build());
Use the service filter in the Console to switch between services.
Enterprise Setup
This section applies to users with a Sailfish Enterprise account. If you're using the Desktop App for local development only, the basic setup above is all you need.
Enterprise users need two configurations — one for local development (Desktop App) and one for staging/production (Sailfish cloud). The SDK should detect which environment it's running in and configure itself accordingly.
Dual-Mode Configuration
import com.sailfish.veritas.Sailfish;
import com.sailfish.veritas.SailfishConfig;
public class Application {
public static void main(String[] args) {
String sfDev = System.getenv("SF_DEV");
String apiKey = System.getenv("SAILFISH_API_KEY");
if ("true".equalsIgnoreCase(sfDev)) {
// Local development — send to Desktop App
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("my-java-service")
.serviceVersion("1.0.0")
.build());
} else if (apiKey != null && !apiKey.isEmpty()) {
// Staging/Production — send to Sailfish cloud
// Do NOT set graphqlEndpoint — the SDK defaults to the cloud endpoint
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey(apiKey)
.serviceIdentifier("my-java-service")
.serviceVersion("1.0.0")
.build());
}
// Start your application...
}
}
Spring Boot Example
@SpringBootApplication
public class Application {
public static void main(String[] args) {
String sfDev = System.getenv("SF_DEV");
String apiKey = System.getenv("SAILFISH_API_KEY");
if ("true".equalsIgnoreCase(sfDev)) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey("sf-veritas-local")
.graphqlEndpoint("http://localhost:6776/graphql/")
.serviceIdentifier("my-spring-service")
.build());
} else if (apiKey != null && !apiKey.isEmpty()) {
Sailfish.setupInterceptors(SailfishConfig.builder()
.apiKey(apiKey)
.serviceIdentifier("my-spring-service")
.build());
}
SpringApplication.run(Application.class, args);
}
}
Environment Variables for Enterprise
Set these in your staging/production deployment:
# Required — your company's Enterprise API key
SAILFISH_API_KEY=your-enterprise-api-key
# Required — the git commit SHA for this build
GIT_SHA=$(git rev-parse HEAD)
# Optional — override the endpoint (only for local dev, do NOT set in production)
# SAILFISH_GRAPHQL_ENDPOINT=http://localhost:6776/graphql/
GIT_SHA
The GIT_SHA environment variable allows Sailfish to correlate telemetry with specific commits. Set it in your build pipeline:
# 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(apiKey)
.gitSha(System.getenv("GIT_SHA"))
.serviceIdentifier("my-java-service")
.build());
URL Override
You can override the GraphQL endpoint in two ways:
- Environment variable:
SAILFISH_GRAPHQL_ENDPOINT - Constructor argument:
graphqlEndpointinSailfishConfig.builder()
| Environment | Endpoint | How |
|---|---|---|
| Local dev | http://localhost:6776/graphql/ | Set graphqlEndpoint() or SAILFISH_GRAPHQL_ENDPOINT |
| Staging/Production | Default (cloud) | Do not set either — the SDK uses https://api-service.sailfishqa.com/graphql/ automatically |