Easily debug Microsoft Graph Java SDK requests

Easily debug Microsoft Graph Java SDK requests

Using the Microsoft Graph Java SDK is a convenient way to connect your app to Microsoft 365. Here's how you can easily debug API requests issued from the SDK.

Build apps that work with Microsoft 365

Microsoft 365 is a platform where millions of users work together every day. You can create apps that use the data and insights from Microsoft 365 to help them do their work more efficiently and effectively.

Microsoft Graph is the API that connects you to your organization's data in Microsoft 365. You can access it using its REST endpoints. If you build an app connected to Microsoft 365 using Java, you should consider using the Microsoft Graph Java SDK. The SDK makes authentication easier and handles complex scenarios like backing off when throttled, following redirects and uploading large files, which means that you can focus on building your app instead of its plumbing!

Use the Microsoft Graph Java SDK to connect your Java app to Microsoft 365

To begin using the Microsoft Graph Java SDK, create a Graph client with an authentication provider. Then, you can start calling Microsoft Graph to access and manipulate data in Microsoft 365.

For building a deamon app, you'd use code similar to following:

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;

final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
    .clientId(clientId)
    .clientSecret(clientSecret)
    .tenantId(tenantId)
    .build();
final TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider(credential);
final GraphServiceClient<okhttp3.Request> graphClient = GraphServiceClient.builder()
    .authenticationProvider(authProvider)
    .buildClient();

Debug Microsoft Graph Java SDK API requests

When you start building your app, it can happen that you'll get an error calling Microsoft Graph. Looking at the returned error information should give you a clearer idea of what's wrong and how to fix it.

If that's not the case though, and you need to see the request and response that goes over the wire, here's a trick that you can use.

Microsoft Graph Java SDK middleware

The Microsoft Graph Java SDK supports the concept of middleware: functions that run as part of its request- and response pipeline and have access to the information about the outgoing request and received response. If you want to inspect API requests and responses issued by the Microsoft Graph Java SDK, building a custom middleware is the easiest way about it. Middleware has access to the full request context information which includes information about the outgoing request and received response.

Debug middleware for Microsoft Graph Java SDK

Start, with creating a new file named DebugHandler.java. Add the following code:

package connector;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import okhttp3.Response;
import okio.Buffer;

public class DebugHandler implements okhttp3.Interceptor {
  @Override
  public Response intercept(Chain chain) throws IOException {
    System.out.println("");
    System.out.printf("Request: %s %s%n", chain.request().method(), chain.request().url().toString());
    System.out.println("Request headers:");
    chain.request().headers().toMultimap()
        .forEach((k, v) -> System.out.printf("%s: %s%n", k, String.join(", ", v)));
    if (chain.request().body() != null) {
      System.out.println("Request body:");
      final Buffer buffer = new Buffer();
      chain.request().body().writeTo(buffer);
      System.out.println(buffer.readString(StandardCharsets.UTF_8));
    }

    final Response response = chain.proceed(chain.request());

    System.out.println("");
    System.out.printf("Response: %s%n", response.code());
    System.out.println("Response headers:");
    response.headers().toMultimap()
        .forEach((k, v) -> System.out.printf("%s: %s%n", k, String.join(", ", v)));
    if (response.body() != null) {
      System.out.println("Response body:");
      System.out.println(response.peekBody(Long.MAX_VALUE).string());
    }

    return response;
  }
}

You start with defining a new handler that inherits from the okhttp3.Interceptor class which defines middleware handlers for the Microsoft Graph Java SDK. Next, you overrde the intercept method, that is called on each API request handled by the SDK.

When this function runs as part of the client's middleware, it will first log information about the outgoing request. Then, it will call the next middleware in chain and wait for its execution. This is where the actual API request will be executed. Finally, it will print response result, its headers, and if suitable, the response body.

Use the debug middleware

The last step is to add the debug middleware to the Graph client. To do that, update the client's initiation code as follows:

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.httpcore.HttpClients;
import com.microsoft.graph.requests.GraphServiceClient;
import okhttp3.OkHttpClient;

final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
    .clientId(clientId)
    .clientSecret(clientSecret)
    .tenantId(tenantId)
    .build();
final TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider(credential);
final OkHttpClient okHttpClient = HttpClients.createDefault(authProvider)
    .newBuilder()
    .addInterceptor(new DebugHandler())
    .build();
final GraphServiceClient<okhttp3.Request> graphClient = GraphServiceClient.builder()
    .httpClient(okHttpClient)
    .buildClient();

We changed the Graph client instantiation code, to get the default HTTP client from the Microsoft Graph Java SDK with its default interceptors. For debugging to have access to all request and response information, we need to add it as the last interceptor.

With this setup in place, when you call Microsoft Graph using the client, you'll see the following output in the console:

TIP: To see this middleware in action, check out the sample Microsoft Graph connector.

$ ./gradlew run  

> Task :app:run

Request: GET https://graph.microsoft.com/v1.0/users?%24top=1
Request headers:
accept: */*
authorization: Bearer eyJ0eXAiOiJKV1...
client-request-id: a8cd2ffa-e896-43b6-84b2-6a940552e067
sdkversion: graph-java/v5.75.0, graph-java-core/v2.0.19 (featureUsage=0), java/17.0.8

Response: 200
Response headers:
cache-control: no-cache
client-request-id: a8cd2ffa-e896-43b6-84b2-6a940552e067
content-type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
date: Wed, 01 Nov 2023 14:19:06 GMT
odata-version: 4.0
request-id: ecfd362f-3631-4c4d-aa2c-e8b84eb3911a
strict-transport-security: max-age=31536000
transfer-encoding: chunked
vary: Accept-Encoding
x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"West Europe","Slice":"E","Ring":"5","ScaleUnit":"005","RoleInstance":"AM4PEPF00015137"}}
x-ms-resource-unit: 1
Response body:
{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users","@odata.nextLink":"https://graph.microsoft.com/v1.0/users?$top=1&$skiptoken=RFNwdAIAAQAAAB46QWRlbGVWQDJjemszZy5vbm1pY3Jvc29mdC5jb20pVXNlcl82ZGU4ZWMwNC02Mzc2LTQ5MzktYWI0Ny04M2EyYzg1YWI1ZjW5AAAAAAAAAAAAAA","value":[{"businessPhones":["+1 425 555 0109"],"displayName":"Adele Vance","givenName":"Adele","jobTitle":"Retail Manager","mail":"AdeleV@2czk3g.onmicrosoft.com","mobilePhone":null,"officeLocation":"18/2111","preferredLanguage":"en-US","surname":"Vance","userPrincipalName":"AdeleV@2czk3g.onmicrosoft.com","id":"6de8ec04-6376-4939-ab47-83a2c85ab5f5"}]}

Summary

Using the Microsoft Graph Java SDK is an easy way for you to get the data and insights from Microsoft 365 in your app. The SDK simplifies authentication and handles complex API scenarios for you. If you ever need to debug the requests and responses handled by the SDK, you can build a simple debug middleware to log the raw requests and responses for you.