Enterprise Java

Guide to Logging in Java Jersey Applications

Logging is a critical part of any production-ready RESTful service. It helps us trace requests, debug issues, and monitor performance. In Jersey (the reference implementation of Jakarta REST/JAX-RS), logging can be configured both on the server side (for request/response logging) and on the client side (for outgoing REST requests). This guide explains how to configure both server-side and client-side logging using Jersey.

1. Jersey Logging Basics

Jersey uses the JDK’s built-in logging framework, java.util.logging (JUL), by default. Jersey provides org.glassfish.jersey.logging.LoggingFeature, which logs request and response payloads, headers, and entity content at configurable verbosity levels. This allows automatic HTTP traffic logging without manual statements in each resource.

Jersey logging can be applied at both the server and client levels: on the server, LoggingFeature logs inbound requests and outbound responses at the HTTP layer, providing detailed visibility into interactions, while on the client, the same LoggingFeature captures outgoing requests and incoming responses, enabling effective monitoring and debugging. The Jersey LoggingFeature provides complete observability across both the server and client sides of a RESTful system.

2. Project Setup and Dependencies

Let’s begin with the Maven pom.xml configuration for our Jersey project.

        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>3.1.11</version>
        </dependency>
       
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>3.1.11</version>
        </dependency>
 
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>3.1.11</version>
        </dependency>
        
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
            <version>3.1.11</version>
        </dependency>
        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>2.0.17</version>
            <type>pom</type>
        </dependency>

The jersey-server dependency provides the core server-side APIs for handling HTTP requests, while jersey-client enables making HTTP requests to other REST endpoints. The jersey-hk2 module integrates Jersey with the HK2 dependency injection framework. The jersey-container-grizzly2-http dependency adds support for running the application on the lightweight Grizzly HTTP server.

3. Creating a Simple RESTful Service

Here’s a minimal REST resource class that logs incoming requests.

@Path("/messages")
public class MessageResource {

    private static final Logger logger = LoggerFactory.getLogger(MessageResource.class);

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        logger.info("Received GET request to /messages");
        return "Hello from Jersey!";
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Message echo(Message msg) {
        logger.info("Received POST with payload: {}", msg.getText());
        return msg;
    }

}

This resource defines a GET endpoint that returns a simple text message and a POST endpoint that echoes the received JSON payload, with each request logged using SLF4J at the INFO level.

public class Message {

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

4. Registering LoggingFeature in a ResourceConfig

By using Jersey’s ResourceConfig or a subclass of Application, we can register the LoggingFeature to automatically log all HTTP traffic handled by the application.

public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {

        packages("com.jcg.example");
        register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
                Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));
    }

}

The LoggingFeature is registered in the ResourceConfig class, and using Verbosity.PAYLOAD_ANY enables logging of both request and response headers and bodies. This configuration ensures that every request to the server is automatically logged without requiring additional code in the resource classes.

Starting the Server

We’ll start the Jersey application using the Grizzly HTTP server for simplicity.

public class JerseyLoggingGuide {

    private static final Logger logger = Logger.getLogger(JerseyLoggingGuide.class.getName());
    public static final String BASE_URI = "http://localhost:8080/api/";

    public static HttpServer startServer() {
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), new JerseyApplication());
    }

    public static void main(String[] args) {
        final HttpServer server = startServer();
        logger.log(Level.INFO, "Jersey application started at {0}", BASE_URI);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            server.shutdownNow();
            logger.info("Jersey server stopped.");
        }));
    }
}

The server runs locally on port 8080 and registers our JerseyApplication configuration.
Once started, it serves requests to /api.

5. Configuring Client-Side Logging

Jersey’s client API supports the same LoggingFeature for outgoing HTTP requests.

public class MessageClient {

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MessageClient.class);
    private static final String BASE_URI = "http://localhost:8080/api/messages";

    public static void main(String[] args) {
        // Create client configuration and register LoggingFeature
        ClientConfig config = new ClientConfig();
        config.register(new LoggingFeature(
                Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
                Level.INFO,
                LoggingFeature.Verbosity.PAYLOAD_ANY,
                10000
        ));

        // Build client with logging configuration
        Client client = ClientBuilder.newClient(config);
        WebTarget target = client.target(BASE_URI);

        Response response = target.request().get();
        logger.info("GET Response: {}", response.readEntity(String.class));

        Message message = new Message();
        message.setText("Hello from client!");
        Response postResponse = target.request(MediaType.APPLICATION_JSON)
                .post(Entity.entity(message, MediaType.APPLICATION_JSON));
        logger.info("POST Response: {}", postResponse.readEntity(String.class));
    }

}

In this example, we create a ClientConfig object and register the LoggingFeature to capture client-side HTTP interactions. The LoggingFeature is configured with the default Jersey logger, a logging level of INFO, and a verbosity mode of PAYLOAD_ANY, which logs both headers and entity bodies for requests and responses. The last parameter (10000) defines the maximum entity size (in bytes) to include in the logs.

By building the client with this configuration, all outgoing requests and incoming responses are automatically logged, making it easier to monitor and debug REST client activity. This setup provides consistent visibility into client-server communication without requiring additional manual logging code.

6. Running and Viewing Logs

Start the server:

mvn compile exec:java -Dexec.mainClass="com.jcg.example.JerseyLoggingGuide"

Then run the client:

mvn compile exec:java -Dexec.mainClass="com.jcg.example.MessageClient"

Example Output

Server output is:

Nov 09, 2025 4:57:33 P.M. org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:8080]
Nov 09, 2025 4:57:33 P.M. org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Nov 09, 2025 4:57:33 P.M. com.jcg.example.JerseyLoggingGuide main
INFO: Jersey application started at http://localhost:8080/api/
Nov 09, 2025 4:59:05 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 1 * Server has received a request on thread grizzly-http-server-0
1 > GET http://localhost:8080/api/messages
1 > accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
1 > accept-encoding: gzip, deflate, br, zstd
1 > accept-language: en-US,en;q=0.9
1 > connection: keep-alive
1 > dnt: 1
1 > host: localhost:8080
1 > sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"
1 > sec-ch-ua-mobile: ?0
1 > sec-ch-ua-platform: "macOS"
1 > sec-fetch-dest: document
1 > sec-fetch-mode: navigate
1 > sec-fetch-site: none
1 > sec-fetch-user: ?1
1 > upgrade-insecure-requests: 1
1 > user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36

Nov 09, 2025 4:59:06 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 1 * Server responded with a response on thread grizzly-http-server-0
1 < 200
1  GET http://localhost:8080/api/messages
2 > accept: */*
2 > connection: keep-alive
2 > host: localhost:8080
2 > user-agent: Jersey/3.1.11 (HttpUrlConnection 21.0.2)

Nov 09, 2025 6:25:01 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 2 * Server responded with a response on thread grizzly-http-server-1
2 < 200
2  GET http://localhost:8080/api/messages
3 > accept: */*
3 > connection: keep-alive
3 > host: localhost:8080
3 > user-agent: Jersey/3.1.11 (HttpUrlConnection 21.0.2)

Nov 09, 2025 6:30:53 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 3 * Server responded with a response on thread grizzly-http-server-2
3 < 200
3  POST http://localhost:8080/api/messages
4 > accept: application/json
4 > connection: keep-alive
4 > content-length: 29
4 > content-type: application/json
4 > host: localhost:8080
4 > user-agent: Jersey/3.1.11 (HttpUrlConnection 21.0.2)
{"text":"Hello from client!"}

Nov 09, 2025 6:30:54 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 4 * Server responded with a response on thread grizzly-http-server-3
4 < 415

Client output is:

Nov 09, 2025 6:30:53 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 1 * Sending client request on thread com.jcg.example.MessageClient.main()
1 > GET http://localhost:8080/api/messages

Nov 09, 2025 6:30:53 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 1 * Client response received on thread com.jcg.example.MessageClient.main()
1 < 200
1 < Content-Length: 18
1  POST http://localhost:8080/api/messages
2 > Accept: application/json
2 > Content-Type: application/json
{"text":"Hello from client!"}

Nov 09, 2025 6:30:54 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 2 * Client response received on thread com.jcg.example.MessageClient.main()
2 < 415
2 < Content-Length: 0

7. Advanced: Custom Logging Filter

While LoggingFeature provides comprehensive HTTP-level logging, sometimes you may need more control over how logs are captured or formatted, for example, logging request URIs, response statuses, or processing times in a custom format. Jersey allows you to implement your own logging filter by extending the ContainerRequestFilter and ContainerResponseFilter interfaces.

Here’s an example of a custom logging filter that logs request and response information.

package com.jcg.example.filter;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;


@Provider
public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {

    private static final Logger logger = Logger.getLogger(CustomLoggingFilter.class.getName());

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String method = requestContext.getMethod();
        String uri = requestContext.getUriInfo().getRequestUri().toString();
        logger.log(Level.INFO, "Incoming request: {0} {1}", new Object[]{method, uri});
    }

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
            throws IOException {
        int status = responseContext.getStatus();
        logger.log(Level.INFO, "Outgoing response: HTTP {0}", status);
    }
}

This custom filter logs basic request and response information, including the HTTP method and URI when a request is received, and the HTTP status code when a response is sent back. We can register this filter in our JerseyApplication class alongside other configurations:

public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {

        packages("com.jcg.example", "com.example.filters");
        register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
                Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));
    }

}

When you run the application and send a request, you’ll see logs similar to this:

Nov 09, 2025 7:26:03 P.M. com.jcg.example.filter.CustomLoggingFilter filter
INFO: Incoming request: GET http://localhost:8080/api/messages
Nov 09, 2025 7:26:03 P.M. com.jcg.example.filter.CustomLoggingFilter filter
INFO: Outgoing response: HTTP 200
Nov 09, 2025 7:26:03 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 1 * Server responded with a response on thread grizzly-http-server-0
1 < 200
1  GET http://localhost:8080/api/messages
2 > accept: */*
2 > connection: keep-alive
2 > host: localhost:8080
2 > user-agent: Jersey/3.1.11 (HttpUrlConnection 21.0.2)

Nov 09, 2025 7:27:09 P.M. com.jcg.example.filter.CustomLoggingFilter filter
INFO: Incoming request: GET http://localhost:8080/api/messages
Nov 09, 2025 7:27:09 P.M. com.jcg.example.filter.CustomLoggingFilter filter
INFO: Outgoing response: HTTP 200
Nov 09, 2025 7:27:09 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 2 * Server responded with a response on thread grizzly-http-server-1
2 < 200
2  POST http://localhost:8080/api/messages
3 > accept: application/json
3 > connection: keep-alive
3 > content-length: 29
3 > content-type: application/json
3 > host: localhost:8080
3 > user-agent: Jersey/3.1.11 (HttpUrlConnection 21.0.2)
{"text":"Hello from client!"}

Nov 09, 2025 7:27:10 P.M. com.jcg.example.filter.CustomLoggingFilter filter
INFO: Incoming request: POST http://localhost:8080/api/messages
Nov 09, 2025 7:27:10 P.M. com.jcg.example.filter.CustomLoggingFilter filter
INFO: Outgoing response: HTTP 200
Nov 09, 2025 7:27:11 P.M. org.glassfish.jersey.logging.LoggingInterceptor log
INFO: 3 * Server responded with a response on thread grizzly-http-server-2
3 < 200
3 < Content-Type: application/json
{"text":"Hello from client!"}

The custom filter logs clear request and response entries (Incoming request / Outgoing response), while the resource class logs capture activity within your application logic, and combined with LoggingFeature, we also obtain detailed payload-level logging for full traceability.

8. Conclusion

In this article, we explored how to implement and configure logging in a Jersey-based RESTful application, covering both server-side and client-side scenarios. We demonstrated the use of Jersey’s LoggingFeature for automatic HTTP traffic logging, integrating SLF4J or java.util.logging for centralized application logs, and creating a custom logging filter for more control over request and response traces. By combining these approaches, we can achieve comprehensive observability, making it easier to monitor, debug, and maintain RESTful services effectively.

9. Download the Source Code

This article provided a guide on Java Jersey logging for server-side applications.

Download
You can download the full source code of this example here: java jersey logging server

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button