Avo Inspector Java SDK

Quick Start Guide

Find the Quick Start Guide in our GitHub repo.

Installation

The SDK requires Java 8 or newer. We host the library on JitPack.io, so

add the following to the root build.gradle:

repositories {
    mavenCentral()
    ...
    maven { url 'https://jitpack.io' }
}

and:

dependencies {
    implementation 'com.github.avohq:java-avo-inspector:TAG'
}

With Maven, add the JitPack repository and the dependency to the pom.xml:

<repositories>
  <repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
  </repository>
</repositories>
 
<dependencies>
  <dependency>
    <groupId>com.github.avohq</groupId>
    <artifactId>java-avo-inspector</artifactId>
    <version>TAG</version>
  </dependency>
</dependencies>

Use the latest GitHub release tag (e.g. 2.0.0) as TAG to get the latest version of the library.

Import

import is.avo.inspector.AvoEventSchemaType;
import is.avo.inspector.AvoInspector;
import is.avo.inspector.AvoInspectorEnv;
import is.avo.inspector.AvoInspectorOptions;
import is.avo.inspector.AvoInspectorTarget;
import is.avo.inspector.Inspector;
import is.avo.inspector.InspectorEvent;

Initialization

Obtain the API key in Inspector tab in your Avo.app workspace.

You will need to create an instance of AvoInspector with the constructor.

public AvoInspector(@NotNull String apiKey, @NotNull String appVersion, @NotNull String appName, @NotNull AvoInspectorEnv env)

Parameters:

  • String apiKey - the API key you get in Inspector tab of your Avo workspace
  • String appVersion - app version to attribute the events to
  • String appName - application name
  • AvoInspectorEnv env - current environment: AvoInspectorEnv.Dev, AvoInspectorEnv.Staging or AvoInspectorEnv.Prod

The constructor throws IllegalArgumentException when the API key or the app version is blank, or when the API key contains a control character other than tab.

You can also build the instance with AvoInspectorOptions, which takes the batching options as well:

AvoInspector avoInspector = new AvoInspector(AvoInspectorOptions.builder()
        .apiKey("MY_API_KEY")
        .env(AvoInspectorEnv.Prod)    // or a String: "dev", "staging" or "prod"
        .appVersion("1.0.0")
        .appName("My App Name")       // optional, defaults to ""
        .batchSize(30)                // optional, default 30; always 1 in dev
        .batchFlushSeconds(30)        // optional, default 30
        .maxQueueSize(1000)           // optional, default 1000
        .disableBatchTimer(false)     // optional, default false; set true in serverless functions
        .blockWhenBacklogged(false)   // optional, default false; set true in backfill and batch jobs
        .build());

Create one instance per API key and app when your application starts, and share it. The SDK is safe to use from multiple threads; don’t create an instance per request.

Environment

If env is missing or null, or (when passed as a String) anything other than exactly "dev", "staging" or "prod", the SDK does not throw. It falls back to dev and always prints this to stderr:

Avo Inspector: Invalid env "<value>", falling back to "dev".

A dev instance turns logging on for the whole process and sends every event immediately, one request per event (batchSize is always 1 in dev).

⚠️

Always pass the env explicitly in production, e.g. AvoInspectorEnv.Prod. Without it your production instance runs as dev: it logs every event and sends one request per event.

Sending event schemas to Avo Inspector

This is the core of the Avo Inspector SDK.

Call **one of the methods** in this section every time an event is tracked.

Option 1

@NonNull Map<String, AvoEventSchemaType> trackSchemaFromEvent(@Nullable InspectorEvent event);

Example usage:

void trackEvent(@NonNull String eventName, @Nullable Map<String, ?> eventParams) {
    tracker.track(eventName, eventParams);
    this.avoInspector.trackSchemaFromEvent(InspectorEvent.builder()
            .eventName(eventName)
            .eventProperties(eventParams)
            .build());
}

Extracts the event schema from the event’s properties and sends the schema to Avo for analysis. Since 2.0.0 this is the only form of trackSchemaFromEvent: the 1.x trackSchemaFromEvent(eventName, eventProperties) and trackSchemaFromEvent(eventName, eventProperties, target) are removed.

Parameters:

  • InspectorEvent event - built with InspectorEvent.builder(). Every value is optional:
    • eventName(String) - event name, sometimes referred as event type. A null, empty or whitespace-only name is sent as Missing Event Name.
    • eventProperties(Map<String, ?>) or eventProperties(JSONObject) - actual event properties, which will be converted to event schema and the event schema will be sent to Avo. Resulting keys will be JSON fields keys and resulting values will be JSON fields values types converted to schema types.
    • streamId(String) - any correlation id you choose.
    • outputReference(String), originHint(String), originAppVersion(String) - the gateway coordinates. With a gateway-scoped API key, always pass originHint and originAppVersion. Pass outputReference when the payload was bound for a specific output; leave it out for an observation at the gateway checkpoint.
    • target(AvoInspectorTarget) - sends the event to another Avo source.

A null event sends nothing and returns an empty schema.

Example format:
JSONObject eventProperties = new JSONObject();
eventProperties.put("userId", 1337);
eventProperties.put("emailAddress", "jane.doe@avo.app");
eventProperties.put("key", "value");

Return Type:

  • @NonNull Map<String, AvoEventSchemaType> containing event schema, so you can verify that conversion was correct. Example format:
Map<String, AvoEventSchemaType> eventSchema = new HashMap<>();
eventSchema.put("userId", new AvoEventSchemaType.AvoInt());
eventSchema.put("emailAddress", new AvoEventSchemaType.AvoString());
eventSchema.put("key", newAvoEventSchemaType.AvoString());

Option 2

void trackSchema(@NonNull String eventName, @Nullable Map<String, AvoEventSchemaType> eventSchema);

This method allows you to process the event schema before sending it. It’s handy to extract the schema from your event properties with extractSchema(Object eventProperties) (see below), process it and then provide it to this method.

Parameters:

  • @NonNull String eventName - event name, also known as event type.
  • @Nullable Map<String, AvoEventSchemaType> eventSchema - actual event schema that will be sent to Avo.
Example format:
Map<String, AvoEventSchemaType> eventSchema = new HashMap<>();
eventSchema.put("userId", new AvoEventSchemaType.AvoInt());
eventSchema.put("emailAddress", new AvoEventSchemaType.AvoString());
eventSchema.put("key", new AvoEventSchemaType.AvoString());

See this for details about event schema structure and schema types.

Flushing and shutdown

Outside dev, events are buffered in memory and sent in batches: when batchSize events are buffered, when the oldest buffered event is batchFlushSeconds old, or when you call flush(). In dev every event is sent immediately. When more than maxQueueSize events are buffered the oldest are dropped.

In a backfill, import or other batch job, set blockWhenBacklogged(true): a track call that leaves 1,000 or more events waiting for a send slot then waits until fewer are waiting, at most 10 seconds per call, which paces the loop. It does not guarantee delivery: while the API is slow or down for longer, the oldest events are still dropped (and logged). An interrupt or destroy() ends the wait. Don’t set it for request threads in a server, since they would wait whenever the Inspector API is slow.

Nothing is written to disk and failed requests are not retried. The SDK’s threads are daemon threads, so they don’t keep the JVM alive. Call flush() before your process exits, and before a serverless handler returns, or buffered and in-flight events are lost.

boolean flush();                // sends buffered events, waits up to 10 seconds for in-flight requests
boolean flush(long timeoutMs);  // same, with a custom timeout in milliseconds

flush() returns true when nothing is left buffered, waiting or in flight: drained, not delivered (failed sends and dropped events are reported on stderr). It never throws and the instance stays usable afterwards. In serverless functions also set disableBatchTimer(true).

void destroy();

destroy() stops the instance: buffered events are discarded unsent, in-flight requests are abandoned and later track calls do nothing. Call flush() first if you want buffered events sent.

As a safety net, a JVM shutdown hook flushes every live instance, waiting up to 10 seconds in total. It runs when the JVM exits normally (for example when main returns or System.exit is called) or receives SIGTERM. It does not run on SIGKILL, Runtime.halt() or a JVM crash, and a serverless runtime may freeze or kill the process without running it, so an explicit flush() is still required. When the hook runs out of time it prints how many events it could not send (unsent at exit) or could not confirm (unconfirmed at exit) to stderr.

In a servlet container or any other environment that unloads and reloads your application, call flush() and then destroy() on every instance when your application stops, for example in ServletContextListener.contextDestroyed. Otherwise the SDK keeps sending buffered events after your application is unloaded, and its shutdown hook and threads keep the old class loader in memory.

Example usage:

@Override
public void contextDestroyed(ServletContextEvent sce) {
    avoInspector.flush();
    avoInspector.destroy();
}

Custom Inspector implementations

AvoInspector implements the Inspector interface: trackSchemaFromEvent(InspectorEvent), trackSchema, extractSchema, flush(), flush(long timeoutMs) and destroy(). In 2.0 the interface has no default methods, so an implementation written for 1.x no longer compiles: it must implement the 2.0 interface. Constructors of AvoInspector are unchanged.

If your Inspector wraps an AvoInspector, forward every method, flush and destroy included. Otherwise buffered events are never flushed and the wrapped instance is never stopped.

class MyInspector implements Inspector {
    private final AvoInspector inner;
 
    MyInspector(AvoInspector inner) {
        this.inner = inner;
    }
 
    @Override
    public @NotNull Map<String, AvoEventSchemaType> trackSchemaFromEvent(@Nullable InspectorEvent event) {
        return inner.trackSchemaFromEvent(event);
    }
 
    // ... trackSchema and extractSchema, forwarded to inner the same way ...
 
    @Override
    public boolean flush() {
        return inner.flush();
    }
 
    @Override
    public boolean flush(long timeoutMs) {
        return inner.flush(timeoutMs);
    }
 
    @Override
    public void destroy() {
        inner.destroy();
    }
}

Other methods

1. Extract schema from event properties

@NonNull Map<String, AvoEventSchemaType> extractSchema(@Nullable Object eventProperties);

This is the method used by trackSchemaFromEvent internally. Extracts event schema in form of Map<String, AvoEventSchemaType> from an event properties object. It never throws and makes no network calls.

Parameters:

  • @Nullable Object eventProperties - event properties object. If it is an instance of JSONObject or Map<?, ?> it will be processed based on the containing key-value pairs, otherwise the event schema will be based on object fields.

Return Type:

  • @NonNull Map<String, AvoEventSchemaType> containing event schema of the given event properties. Keys are event properties names and values are event properties types.
Example format:
Map<String, AvoEventSchemaType> eventSchema = new HashMap<>();
eventSchema.put("userId", new AvoEventSchemaType.AvoInt());
eventSchema.put("emailAddress", new AvoEventSchemaType.AvoString());
eventSchema.put("key", newAvoEventSchemaType.AvoString());

2. Print logs

static void enableLogging(boolean enable); // static method on AvoInspector

enableLogging controls printing of tracked event schemas and other helpful information to stdout. It is enabled by default in dev and disabled in staging and prod. The flag is process-wide: it applies to every instance, and each new instance sets it from its own env. Do not enable it in production. Logs show event names, property names and types, never property values.

Some messages are always printed to stderr, whatever this flag: failed sends, non-200 responses, dropped events, internal errors and configuration warnings such as an invalid env.

Parameters:

  • boolean enable - sets whether Avo Inspector SDK will print logs to stdout.

Avo is the analytics governance platform for product data that lets you define it consistently, implement it correctly and trust it everywhere. Using an AI agent? Connect it to the Avo MCP at https://mcp.avo.app/mcp and it can read and change your tracking plan on a branch.