---
title: Java agent API: Instrumenting example app for external datastore calls and distributed tracing
source: https://docs.newrelic.com/docs/apm/agents/java-agent/api-guides/java-agent-api-instrumenting-example-app-external-datastore-calls-dt
---

This document demonstrates using the New Relic [Java agent API](https://docs.newrelic.com/docs/agents/java-agent/custom-instrumentation/java-agent-api) to instrument a simple client and server application. The instrumentation has these goals:

-   To record external HTTP and datastore [transactions](https://docs.newrelic.com/docs/apm/applications-menu/monitoring/transactions-page).
-   To link external transactions between two applications running New Relic agents (known as [distributed tracing](https://docs.newrelic.com/docs/distributed-tracing) or DT).

See the [Java agent API Javadoc](http://newrelic.github.io/java-agent-api/javadoc/index.html?com/newrelic/api/agent/NewRelic.html) for full descriptions of the available API classes and methods.

> #### ⚠️ IMPORTANT
>
> For best results when using the API, ensure that you have the [latest Java agent release](https://docs.newrelic.com/docs/release-notes/agent-release-notes/java-release-notes). Several APIs used in the examples require Java agent 3.36.0 or higher.

## Client-side example [#complete-client]

Here is an example of the client-side code for a simple client-server application:

**Complete client example code**

````java
package com.newrelic.example;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

<a href="#client-import-java">// New Relic API imports</a>
import com.newrelic.api.agent.ExternalParameters;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.Headers;
import com.newrelic.api.agent.HttpParameters;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.Transaction;

import fi.iki.elonen.NanoHTTPD;
import org.apache.http.HttpMessage;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

public class NewRelicApiExample extends NanoHTTPD {

    <a href="#client-env-details">public NewRelicApiExample() throws IOException, URISyntaxException {</a>
        super(8080);

        // Set Dispatcher name and version
        NewRelic.setServerInfo("NanoHttp", "2.3.1");
        // Set Appserver port for JVM identification
        NewRelic.setAppServerPort(8080);
        // Set JVM instance name
        NewRelic.setInstanceName("Client");

        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
        System.out.println("Running at: http://localhost:8080/");
    }

    public static void main(String[] args) throws URISyntaxException {
        try {
            new NewRelicApiExample();
        } catch (IOException ioe) {
            System.err.println("Unable to start the server:\n" + ioe);
        }
    }

    @Trace(dispatcher = true)
    @Override
    <a href="#client-trace">public Response serve(IHTTPSession session) {</a>
        URI uri = null;
        int status = 0;

        try {
            createDB();
            Thread.sleep(1000);
            uri = new URI("http://localhost:8081");
            status = makeExternalCall(uri);
        } catch (URISyntaxException | InterruptedException | IOException e) {
            e.printStackTrace();
        }

        if (status == 200) {
            return newFixedLengthResponse("<html><body><h1>Successful Response</h1>\n</body></html>\n");
        } else {
            return newFixedLengthResponse("<html><body><h1>Error\n" + status + "</h1>\n</body></html>\n");
        }
    }

    @Trace
    <a href="#client-external">public int makeExternalCall(URI uri) throws IOException {</a>
        HttpUriRequest request = RequestBuilder.get().setUri(uri).build();

        // Wrap the outbound Request object
        Headers outboundHeaders = new HeadersWrapper(request);

        // Obtain a reference to the current transaction
        Transaction transaction = NewRelic.getAgent().getTransaction();
        // Add headers for outbound external request
        transaction.insertDistributedTraceHeaders(outboundHeaders);

        CloseableHttpClient connection = HttpClientBuilder.create().build();
        CloseableHttpResponse response = connection.execute(request);

        // Wrap the incoming Response object
        Headers inboundHeaders = new HeadersWrapper(response);

        // Create an input parameter object for a call to an external HTTP service
        ExternalParameters params = HttpParameters
            .library("HttpClient")
            .uri(uri)
            .procedure("execute")
            .inboundHeaders(inboundHeaders)
            .build();

        // Obtain a reference to the method currently being traced
        TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod();
        // Report a call to an external HTTP service
        tracedMethod.reportAsExternal(params);

        return response.getStatusLine().getStatusCode();
    }

    // Implement Headers interface to create a wrapper for the outgoing Request/incoming Response headers
    <a href="#client-outboundheaders">static class HeadersWrapper implements Headers {</a>
        private final HttpMessage delegate;

        public HeadersWrapper(HttpMessage request) {
            this.delegate = request;
        }

        @Override
        public void setHeader(String name, String value) {
            delegate.setHeader(name, value);
        }

        @Override
        public HeaderType getHeaderType() {
            return HeaderType.HTTP;
        }

        @Override
        public String getHeader(String name) {
            return delegate.getFirstHeader(name).getValue();
        }

        @Override
        public Collection<String> getHeaders(String name) {
            return Arrays.stream(delegate.getHeaders(name))
                .map(NameValuePair::getValue)
                .collect(Collectors.toList());
        }

        @Override
        public void addHeader(String name, String value) {
            delegate.addHeader(name, value);
        }

        @Override
        public Collection<String> getHeaderNames() {
            return Arrays.stream(delegate.getAllHeaders())
                .map(NameValuePair::getName)
                .collect(Collectors.toSet());
        }

        @Override
        public boolean containsHeader(String name) {
            return Arrays.stream(delegate.getAllHeaders())
                .map(NameValuePair::getName)
                .anyMatch(headerName -> headerName.equals(name));
        }
    }

    @Trace
    <a href="#client-database">public void createDB() {</a>
        Connection c = null;
        Statement stmt = null;

        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
            System.out.println("Opened database successfully");

            stmt = c.createStatement();

            String dropSql = "DROP TABLE IF EXISTS COMPANY;";
            stmt.executeUpdate(dropSql);

            String sql = "CREATE TABLE COMPANY " +
                    "(ID INT PRIMARY KEY     NOT NULL," +
                    " NAME           TEXT    NOT NULL, " +
                    " AGE            INT     NOT NULL, " +
                    " ADDRESS        CHAR(50), " +
                    " SALARY         REAL)";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                   "VALUES (1, 'Paul', 32, 'California', 20000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                    "VALUES (2, 'Allen', 25, 'Texas', 15000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
            "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                    "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );";
            stmt.executeUpdate(sql);

            stmt.close();
            c.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
    }
}
```

````

Here is the same client app code divided into sections that describe how the API is used:

**Import Java agent API classes**

This section calls the Java agent API imports used to add distributed tracing to the client application later in the example code.

````java
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

// New Relic API imports
import com.newrelic.api.agent.ExternalParameters;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.Headers;
import com.newrelic.api.agent.HttpParameters;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.Transaction;

import fi.iki.elonen.NanoHTTPD;
import org.apache.http.HttpMessage;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
```

````

**Start the client and set environment details as displayed in the UI**

This section starts up the client server on port 8080 and uses the `NewRelic` class from the API to call the methods `setServerInfo`, `setAppServerPort`, and `setInstanceName`. These API calls affect what is shown in the New Relic UI.

````java
public NewRelicApiExample() throws IOException, URISyntaxException {
    super(8080);

    // Set Dispatcher name and version
    NewRelic.setServerInfo("NanoHttp", "2.3.1");
    // Set Appserver port for jvm identification
    NewRelic.setAppServerPort(8080);
    // Set JVM instance name
    NewRelic.setInstanceName("Client");

    start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    System.out.println("Running at: http://localhost:8080/");
}

public static void main(String[] args) throws URISyntaxException {
    try {
        new NewRelicApiExample();
    } catch (IOException ioe) {
        System.err.println("Unable to start the server:\n" + ioe);
    }
}
```

````

**Start tracing a transaction with the Java agent using @Trace annotation**

This method creates a sample database, sleeps the thread, and makes an external call to the server app listening on port 8081. The `@Trace(dispatcher = true)` annotation tells the agent to start a new transaction when the `serve` method is called, if it is not called as part of an existing transaction (and in this case, it is not). If it were called as part of an existing transaction, it would simply be included as part of that transaction rather than start a new one.

````java
@Trace(dispatcher = true)
@Override
public Response serve(IHTTPSession session) {
    URI uri = null;
    int status = 0;

    try {
        createDB();
        Thread.sleep(1000);
        uri = new URI("http://localhost:8081");
        status = makeExternalCall(uri);
    } catch (URISyntaxException | InterruptedException | IOException e) {
        e.printStackTrace();
    }

    if (status == 200) {
        return newFixedLengthResponse("<html><body><h1>Successful Response</h1>\n</body></html>\n");
    } else {
        return newFixedLengthResponse("<html><body><h1>Error\n" + status + "</h1>\n</body></html>\n");
    }
}
```

````

**Make an external call using the Java agent API**

This section contains the code that initiates distributed tracing to the application making the request. The `@Trace` annotation tells the agent to track this method as part of an existing transaction as started by the `serve` method.

The request object is wrapped by a class that implements the Java agent API's `Headers` interface, which ensures that the proper `HeaderType` is set (in this case `HTTP`). A call to `insertDistributedTraceHeaders` adds the headers to the `request` and the request is sent to the server.

When the `response` returns it is wrapped by a class implementing the Java agent API's `InboundHeaders` interface. `Headers` is a sub interface of `InboundHeaders` and, in this case, the wrapper class could be reused.

The `inboundHeaders`, along with the `"library"`, `URI`, and `"procedure"` arguments, are used to build an `HttpParameters` object. The `params` object is then passed as an argument to the `reportAsExternal` method, which reports the `TracedMethod` as an external HTTP call.

````java
@Trace
public int makeExternalCall(URI uri) throws IOException {
    HttpUriRequest request = RequestBuilder.get().setUri(uri).build();

    // Wrap the outbound Request object
    Headers outboundHeaders = new HeadersWrapper(request);

    // Obtain a reference to the current transaction
    Transaction transaction = NewRelic.getAgent().getTransaction();
    // Add headers for outbound external request
    transaction.insertDistributedTraceHeaders(outboundHeaders);

    CloseableHttpClient connection = HttpClientBuilder.create().build();
    CloseableHttpResponse response = connection.execute(request);

    // Wrap the incoming Response object
    Headers inboundHeaders = new HeadersWrapper(response);

    // Create an input parameter object for a call to an external HTTP service
    ExternalParameters params = HttpParameters
        .library("HttpClient")
        .uri(uri)
        .procedure("execute")
        .inboundHeaders(inboundHeaders)
        .build();

    // Obtain a reference to the method currently being traced
    TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod();
    // Report a call to an external HTTP service
    tracedMethod.reportAsExternal(params);

    return response.getStatusLine().getStatusCode();
}
```

````

**Implement the Java agent API's Headers Interface**

An implementation of the Java agent API's `Headers` Interface is used to wrap the request object of the client server, which in this example is of type `HttpUriRequest`. The request is passed into the constructor of the `HeadersWrapper` class and implementations of the necessary methods are provided.

The `getHeaderType` method returns a `HeaderType` enum that can either be `HeaderType.HTTP` or `HeaderType.MESSAGE`, as defined by the Java agent API. In this example the external call protocol is HTTP, so `HeaderType.HTTP` is returned.

````java
// Implement Headers interface to create a wrapper for the outgoing Request/incoming Response headers
static class HeadersWrapper implements Headers {
    private final HttpMessage delegate;

    public HeadersWrapper(HttpMessage request) {
        this.delegate = request;
    }

    @Override
    public void setHeader(String name, String value) {
        delegate.setHeader(name, value);
    }

    @Override
    public HeaderType getHeaderType() {
        return HeaderType.HTTP;
    }

    @Override
    public String getHeader(String name) {
        return delegate.getFirstHeader(name).getValue();
    }

    @Override
    public Collection<String> getHeaders(String name) {
        return Arrays.stream(delegate.getHeaders(name))
            .map(NameValuePair::getValue)
            .collect(Collectors.toList());
    }

    @Override
    public void addHeader(String name, String value) {
        delegate.addHeader(name, value);
    }

    @Override
    public Collection<String> getHeaderNames() {
        return Arrays.stream(delegate.getAllHeaders())
            .map(NameValuePair::getName)
            .collect(Collectors.toSet());
    }

    @Override
    public boolean containsHeader(String name) {
        return Arrays.stream(delegate.getAllHeaders())
            .map(NameValuePair::getName)
            .anyMatch(headerName -> headerName.equals(name));
    }
}
```

````

**Create a simple database**

This method simply creates an example SQLite database. The `@Trace` annotation tells the agent to track this method as part of an existing transaction as started by the `serve` method.

````java
@Trace
public void createDB() {
    Connection c = null;
    Statement stmt = null;

    try {
        Class.forName("org.sqlite.JDBC");
        c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
        System.out.println("Opened database successfully");

        stmt = c.createStatement();

        String dropSql = "DROP TABLE IF EXISTS COMPANY;";
        stmt.executeUpdate(dropSql);

        String sql = "CREATE TABLE COMPANY " +
                "(ID INT PRIMARY KEY     NOT NULL," +
                " NAME           TEXT    NOT NULL, " +
                " AGE            INT     NOT NULL, " +
                " ADDRESS        CHAR(50), " +
                " SALARY         REAL)";
        stmt.executeUpdate(sql);

        sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
               "VALUES (1, 'Paul', 32, 'California', 20000.00 );";
        stmt.executeUpdate(sql);

        sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                "VALUES (2, 'Allen', 25, 'Texas', 15000.00 );";
        stmt.executeUpdate(sql);

        sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
        "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );";
        stmt.executeUpdate(sql);

        sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );";
        stmt.executeUpdate(sql);

        stmt.close();
        c.close();
    } catch (Exception e) {
        System.err.println(e.getClass().getName() + ": " + e.getMessage());
        System.exit(0);
    }
}
```

````

## Server-side example [#complete-server]

Here is the server-side code for this example application:

**Complete server example code**

````java
package com.newrelic.example;

import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collection;
import java.util.Collections;

<a href="#server-import-java">// New Relic API imports</a>
import com.newrelic.api.agent.DatastoreParameters;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.Headers;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.Transaction;
import com.newrelic.api.agent.TransportType;

import fi.iki.elonen.NanoHTTPD;

public class NewRelicApiServer extends NanoHTTPD {

    <a href="#server-env-details">public NewRelicApiServer() throws IOException, URISyntaxException {</a>
        super(8081);

        // Set Dispatcher name and version
        NewRelic.setServerInfo("NanoHttp", "2.3.1");
        // Set Appserver port for jvm identification
        NewRelic.setAppServerPort(8081);
        // Set JVM instance name
        NewRelic.setInstanceName("Server");

        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
        System.out.println("\nRunning on http://localhost:8081/ \n");
    }

    public static void main(String[] args) throws URISyntaxException {
        try {
            new NewRelicApiServer();
        } catch (IOException ioe) {
            System.err.println("Unable to start the server:\n" + ioe);
        }
    }

    @Trace(dispatcher = true)
    @Override
    <a href="#server-trace">public Response serve(IHTTPSession session) {</a>
        // Obtain a reference to the current Transaction
        Transaction tx = NewRelic.getAgent().getTransaction();
        // Set the name of the current transaction
        NewRelic.setTransactionName("Custom", "ExternalHTTPServer");

        // Wrap the Request object
        Headers req = new HeadersWrapper(session);

        // Set the request for the current transaction and convert it into a web transaction
        tx.acceptDistributedTraceHeaders(TransportType.HTTP, req);

        queryDB();

        return newFixedLengthResponse("<html><body><h1>SuccessfulResponse</h1>\n</body></html>\n");
    }

    // Implement Headers interface to create a wrapper for the outgoing Request/incoming Response headers
    <a href="#server-extendrequest">static class HeadersWrapper implements Headers {</a>
        private final IHTTPSession delegate;

        public HeadersWrapper(IHTTPSession request) {
            this.delegate = request;
        }

        @Override
        public HeaderType getHeaderType() {
            return HeaderType.HTTP;
        }

        @Override
        public String getHeader(String name) {
            return delegate.getHeaders().get(name);
        }

        @Override
        public Collection<String> getHeaders(String name) {
            return Collections.singletonList(getHeader(name));
        }

        @Override
        public void setHeader(String name, String value) {
            delegate.getHeaders().put(name, value);
        }

        @Override
        public void addHeader(String name, String value) {
            delegate.getHeaders().put(name, value);
        }

        @Override
        public Collection<String> getHeaderNames() {
            return delegate.getHeaders().keySet();
        }

        @Override
        public boolean containsHeader(String name) {
            return delegate.getHeaders().containsKey(name);
        }
    }

    @Trace
    <a href="#server-external-call">public void queryDB() {</a>
        Connection c = null;
        Statement stmt = null;
        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
            c.setAutoCommit(false);
            System.out.println("Opened database successfully");

            stmt = c.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM COMPANY;");
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String address = rs.getString("address");
                float salary = rs.getFloat("salary");
                System.out.println("ID = " + id);
                System.out.println("NAME = " + name);
                System.out.println("AGE = " + age);
                System.out.println("ADDRESS = " + address);
                System.out.println("SALARY = " + salary);
                System.out.println();
            }
            rs.close();
            stmt.close();
            c.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
        // Obtain a reference to the method currently being traced
        TracedMethod method = NewRelic.getAgent().getTracedMethod();

        // Create a DatastoreParameters object and report a call to an external datastore service
        method.reportAsExternal(
                DatastoreParameters
                        .product("sqlite")
                        .collection("test.db")
                        .operation("select")
                        .instance("localhost", 8080)
                        .databaseName("test.db")
                        .build());
    }
}
```

````

Here is the same example server code broken into sections that describe how the API is used:

**Import Java agent API classes**

This section shows the relevant Java agent API imports needed to add distributed tracing and reporting of external datastore calls to the server application.

````java
import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collection;
import java.util.Collections;

// New Relic API imports
import com.newrelic.api.agent.DatastoreParameters;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.Headers;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.Transaction;
import com.newrelic.api.agent.TransportType;

import fi.iki.elonen.NanoHTTPD;
```

````

**Start the server and set its info as displayed in UI**

This section starts up the server on port 8081 and uses the `NewRelic` class from the API to call the methods `setServerInfo`, `setAppServerPort`, and `setInstanceName`. These API calls affect what is shown in the APM UI.

````java
public NewRelicApiServer() throws IOException, URISyntaxException {
    super(8081);

    // Set Dispatcher name and version
    NewRelic.setServerInfo("NanoHttp", "2.3.1");
    // Set Appserver port for jvm identification
    NewRelic.setAppServerPort(8081);
    // Set JVM instance name
    NewRelic.setInstanceName("Server");

    start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    System.out.println("\nRunning on http://localhost:8081/ \n");
}

public static void main(String[] args) throws URISyntaxException {
    try {
        new NewRelicApiServer();
    } catch (IOException ioe) {
        System.err.println("Unable to start the server:\n" + ioe);
    }
}
```

````

**Start tracing a transaction and add outbound Response headers**

The `@Trace(dispatcher = true)` annotation tells the agent to start a new transaction when the `serve` method is called if it is not called as part of an existing transaction (which in this case, it is not). If it were called as part of an existing transaction, it would simply be included as part of that transaction rather than start a new one.

A reference to the current `Transaction` is obtained via a call to `getTransaction` and the name of the transaction is set via a call to the `setTransactionName` method.

The request object, which in this example is of type `IHTTPSession`, is then wrapped using a class extending the Java agent API's `Headers` class. The current `Transaction` is then linked to the parent trace via a call to `setWebRequest` which takes the wrapped `ExtendedRequest` as an argument.

A call to the database is then made and the response object is returned.

````java
@Trace(dispatcher = true)
@Override
public Response serve(IHTTPSession session) {
    // Obtain a reference to the current Transaction
    Transaction tx = NewRelic.getAgent().getTransaction();
    // Set the name of the current transaction
    NewRelic.setTransactionName("Custom", "ExternalHTTPServer");

    // Wrap the Request object
    Headers req = new HeadersWrapper(session);

    // Set the request for the current transaction and convert it into a web transaction
    tx.acceptDistributedTraceHeaders(TransportType.HTTP, req);

    queryDB();

    return newFixedLengthResponse("<html><body><h1>SuccessfulResponse</h1>\n</body></html>\n");
}
```

````

**Extend the Java agent API's Headers class**

Another implementation of the Java agent API's `Headers` class is used to wrap the request object of the server, which in this example is of type `IHTTPSession`. The request is passed into the constructor of the `HeadersWrapper` class, which provides implementations of the `getRequestURI`, `getHeader`, `getRemoteUser`, `getParameterNames`, `getParameterValues`, `getAttribute`, `getCookieValue`, `getHeaderType`, and `getMethod` methods.

The `getHeaderType` method returns a `HeaderType` enum that can either be `HeaderType.HTTP` or `HeaderType.MESSAGE`, as defined by the Java agent API. In this example the external call protocol is HTTP so `HeaderType.HTTP` is returned.

````java
// Implement Headers interface to create a wrapper for the outgoing Request/incoming Response headers
static class HeadersWrapper implements Headers {
    private final IHTTPSession delegate;

    public HeadersWrapper(IHTTPSession request) {
        this.delegate = request;
    }

    @Override
    public HeaderType getHeaderType() {
        return HeaderType.HTTP;
    }

    @Override
    public String getHeader(String name) {
        return delegate.getHeaders().get(name);
    }

    @Override
    public Collection<String> getHeaders(String name) {
        return Collections.singletonList(getHeader(name));
    }

    @Override
    public void setHeader(String name, String value) {
        delegate.getHeaders().put(name, value);
    }

    @Override
    public void addHeader(String name, String value) {
        delegate.getHeaders().put(name, value);
    }

    @Override
    public Collection<String> getHeaderNames() {
        return delegate.getHeaders().keySet();
    }

    @Override
    public boolean containsHeader(String name) {
        return delegate.getHeaders().containsKey(name);
    }
}
```

````

**Instrument an external call to a datastore**

This method makes an external call to the SQLite database that is created by the client. The `@Trace` annotation tells the agent to track this method as part of an existing transaction as started by the `serve` method.

A reference to the current `TracedMethod` is obtained via a call to `getTracedMethod`. A `DatastoreParameters` object is then created using the builder pattern. The `ExternalParameters` object is then passed as an argument to the `reportAsExternal` method, which has the effect of reporting the `TracedMethod` as an external datastore call.

````java
@Trace
public void queryDB() {
    Connection c = null;
    Statement stmt = null;
    try {
        Class.forName("org.sqlite.JDBC");
        c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
        c.setAutoCommit(false);
        System.out.println("Opened database successfully");

        stmt = c.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM COMPANY;");
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            String address = rs.getString("address");
            float salary = rs.getFloat("salary");
            System.out.println("ID = " + id);
            System.out.println("NAME = " + name);
            System.out.println("AGE = " + age);
            System.out.println("ADDRESS = " + address);
            System.out.println("SALARY = " + salary);
            System.out.println();
        }
        rs.close();
        stmt.close();
        c.close();
    } catch (Exception e) {
        System.err.println(e.getClass().getName() + ": " + e.getMessage());
        System.exit(0);
    }
    // Obtain a reference to the method currently being traced
    TracedMethod method = NewRelic.getAgent().getTracedMethod();

    // Create a DatastoreParameters object and report a call to an external datastore service
    method.reportAsExternal(
            DatastoreParameters
                    .product("sqlite")
                    .collection("test.db")
                    .operation("select")
                    .instance("localhost", 8080)
                    .databaseName("test.db")
                    .build());
}
```

````
