Build a custom Java EE interceptor for logging

Java EE interceptors let you place logging around business-method calls without adding System.out.println statements to every session bean or other EJB service. The result is consistent timing, outcome and exception reporting from one reusable class.

This walkthrough uses Jakarta EE names (jakarta.*) and Java’s built-in java.util.logging, so no extra logging dependency is required. For an older Java EE codebase that still uses javax.*, align the imports with its platform version rather than copying packages blindly.

Set up the NetBeans project

Open Apache NetBeans with Jakarta EE support, choose a JDK supported by your NetBeans release, and make sure a compatible runtime such as GlassFish or Payara is available. Select an archetype matching the Jakarta EE version rather than mixing Java EE 8 and Jakarta EE 10 APIs.

A repeatable route is the Maven archetype setup, which covers project creation in NetBeans. Choose an EJB application or module archetype for this example and let Maven manage the Jakarta APIs.

If the generated POM already includes the API, do not add it again. Otherwise, declare it with provided scope because the application server supplies those classes at runtime.

<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>10.0.0</version>
    <scope>provided</scope>
</dependency>

Define a safe logging contract

An EJB interceptor applies to every business method invoked through the container. That broad reach is useful for a platform-wide audit trail, but choose fields carefully: method name, call ID, timestamp, outcome and duration are usually enough. Self-invocation inside the same bean bypasses the proxy and will not pass through the interceptor.

Do not log method arguments or return objects by default: account identifiers, order payloads and authentication data may be sensitive. Logging policy should therefore define redaction before the first deployment, including checks for exception messages that might contain request data.

If only selected CDI beans should be logged, use an @InterceptorBinding and annotate those targets. The around-invoke body remains the same, while the binding prevents unrelated services from being intercepted.

Implement the around-invoke interceptor

Create the class in a dedicated package such as au.com.example.logging. For a standard EJB interceptor, use the imports below.

package au.com.example.logging;

import jakarta.ejb.AroundInvoke;
import jakarta.ejb.Interceptor;
import jakarta.ejb.InvocationContext;
import java.time.Instant;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

@Interceptor
public class LoggingInterceptor {

    private static final Logger LOG =
            Logger.getLogger(LoggingInterceptor.class.getName());

    @AroundInvoke
    public Object record(InvocationContext context) throws Exception {
        String callId = UUID.randomUUID().toString();
        String method = context.getMethod().getName();

        LOG.info(() -> "callId=" + callId
                + " timestamp=" + Instant.now()
                + " event=start method=" + method);

        long started = System.nanoTime();
        try {
            Object result = context.proceed();
            long elapsedMs = (System.nanoTime() - started) / 1_000_000;

            LOG.info(() -> "callId=" + callId
                    + " timestamp=" + Instant.now()
                    + " event=success method=" + method
                    + " elapsedMs=" + elapsedMs);

            return result;
        } catch (Exception failure) {
            LOG.log(Level.SEVERE,
                    "callId=" + callId
                    + " timestamp=" + Instant.now()
                    + " event=failure method=" + method,
                    failure);
            throw failure;
        }
    }
}

The registration annotation makes the class available to the EJB container, while context.proceed() invokes the real business method. The logger is shared safely, while each call keeps its own ID and timing values, preventing concurrent requests from overwriting one another.

Re-throwing the exception preserves transaction and exception mapping. For a CDI project, change the interceptor imports to jakarta.interceptor.Interceptor, AroundInvoke and InvocationContext, then apply an interceptor binding to the target bean.

Run and inspect the invocation log

Clean and build the Maven project, deploy it to the selected server, and call the session bean through a test client or REST endpoint. At the default INFO level, the interceptor’s messages appear in the server log and NetBeans Output window.

Give every event a stable correlation ID. This serves the same purpose as preserving distinctions among roulette’s 38 numbers: when many calls occur in the same second, the ID shows which start and completion belong together.

INFO callId=8f07... event=start method=findOrder
INFO callId=8f07... event=success method=findOrder elapsedMs=18

Reproduce one success and one failure, then filter the output by callId rather than method name alone. If INFO is too noisy, use a JUL logging.properties file to adjust handlers, levels and the timestamp format without changing business code.

Make the interceptor production-ready in Australia

Java EE remains common in established Australian platforms, particularly finance, logistics and retail systems serving Sydney, Melbourne and Brisbane. Put a jurisdiction or policy-version field in structured events where relevant: the changing rules described in bitcoin casino legality are a reminder that operating assumptions must be checked against current Australian rules, not inferred from another market.

Store timestamps in UTC and ISO format, then convert them for local displays, especially across daylight-saving changes in Australian Eastern Standard Time and Summer Time. Sydney and Melbourne shift between AEST and AEDT, while Brisbane remains on AEST. Australian teams commonly expect day-first dates, but machine logs should remain unambiguous. The Privacy Act 1988 and Australian Privacy Principles favour data minimisation, so keep customer and staff names out of routine events.

For Australian commerce logs, AUD amounts and GST context may be relevant, but card numbers, bank details and authentication tokens should never become logging fields.

  • Keep the interceptor in a dedicated package, separate from business interfaces.
  • Use correlation IDs, UTC timestamps, method names, outcomes and elapsed times as default fields.
  • Redact credentials, tokens, card numbers and customer payloads before they reach an appender.
  • Use INFO for lifecycle events, WARNING for retries and SEVERE for failures; reserve FINE for temporary diagnostics.
  • Rotate log files, cap their sizes and restrict production-log access according to Australian organisational retention policies.

Keep the interceptor thin: it observes timing and outcomes, then delegates the real work through context.proceed(). With container registration and deployment logging configuration handling the surrounding duties, the same implementation provides repeatable evidence across local tests and Australian production environments.