DEV Community

realNameHidden
realNameHidden

Posted on

How Do You Log Exceptions Without Exposing Sensitive Details to Clients in Spring Boot?

Learn how to log exceptions safely in Spring Boot without exposing sensitive details to clients. Beginner-friendly, end-to-end Java 21 examples.

Primary Keyword: Logging Exceptions Securely in Spring Boot

Secondary Keywords: Java programming, learn Java, Spring Boot exception handling


Introduction

Imagine visiting your bank’s website and seeing an error like this:


NullPointerException at com.bank.payment.CardService.process(CardService.java:143)

Enter fullscreen mode Exit fullscreen mode

Scary, right? 😨

Not only is it confusing, but it also exposes internal code details—something attackers love.

In Spring Boot, exceptions are inevitable. Databases go down, validations fail, and unexpected bugs happen. The real challenge is logging full details for developers while showing safe, friendly messages to clients.

In this blog, you’ll learn how to log exceptions without exposing sensitive details to clients in Spring Boot, using clear explanations, real-world analogies, and an end-to-end Java 21 example.


Core Concepts

The Golden Rule

Log everything internally, expose nothing sensitive externally.

Think of it like a hospital:

  • 🩺 Doctors need full medical reports (logs)
  • 🧑‍🤝‍🧑 Patients need simple explanations (“You’ll be okay”)

Your API should behave the same way.


What Counts as Sensitive Information?

Never expose:

  • Stack traces
  • Database details
  • Internal class names
  • SQL queries
  • API keys or tokens
  • File paths

Key Spring Boot Tools

  1. SLF4J Logger – For structured logging
  2. @ControllerAdvice – Centralized exception handling
  3. @ExceptionHandler – Map exceptions to safe responses
  4. Custom Error Responses – Clean, client-friendly messages

Code Examples (End-to-End)

Let’s build a secure exception logging setup step by step.


✅ Example 1: Log Full Exception, Return Safe Response

Step 1: Service Layer (Exception Occurs Here)

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    public String processPayment(String cardNumber) {

        if (cardNumber == null) {
            // Simulating a real failure
            throw new IllegalStateException("Card number cannot be null");
        }

        return "Payment processed successfully";
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: REST Controller (Clean, No Try-Catch)

package com.example.demo.controller;

import com.example.demo.service.PaymentService;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/payments")
public class PaymentController {

    private final PaymentService paymentService;

    public PaymentController(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @PostMapping
    public String makePayment(@RequestParam(required = false) String cardNumber) {
        return paymentService.processPayment(cardNumber);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Global Exception Handler (Secure Logging)

package com.example.demo.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log =
            LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<Map<String, Object>> handleIllegalState(
            IllegalStateException ex) {

        // 🔒 Log full details for developers
        log.error("Payment processing failed", ex);

        // 🧑 Client gets safe message only
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(buildSafeErrorResponse(
                        "Payment could not be processed. Please try again later."));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleGenericException(
            Exception ex) {

        log.error("Unexpected system error", ex);

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(buildSafeErrorResponse(
                        "Unexpected error occurred. Contact support."));
    }

    private Map<String, Object> buildSafeErrorResponse(String message) {
        return Map.of(
                "timestamp", LocalDateTime.now(),
                "message", message
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

🔍 Result

curl --location --request POST 'http://localhost:8080/payment'
Enter fullscreen mode Exit fullscreen mode
{
    "message": "Unexpected error occurred. Contact support.",
    "timestamp": "2025-12-24T17:42:58.4423334"
}
Enter fullscreen mode Exit fullscreen mode
curl --location --request POST 'http://localhost:8080/payments?cardNumber=null'
Enter fullscreen mode Exit fullscreen mode
{
    "message": "Payment could not be processed. Please try again later.",
    "timestamp": "2025-12-24T17:43:58.5476755"
}
Enter fullscreen mode Exit fullscreen mode

❌ Client Response

{
  "timestamp": "2025-01-01T12:30:00",
  "message": "Payment could not be processed. Please try again later."
}
Enter fullscreen mode Exit fullscreen mode

✅ Application Logs

ERROR Payment processing failed
java.lang.IllegalStateException: Card number cannot be null
    at com.example.demo.service.PaymentService.processPayment(...)
Enter fullscreen mode Exit fullscreen mode

✔ Sensitive details stay in logs
✔ Clients see clean messages


✅ Example 2: Log Correlation ID for Production Debugging

Why This Matters

In production, logs can be huge. A correlation ID helps you track one request across logs—without exposing internals.


Step 4: Add Correlation ID to Error Response

package com.example.demo.exception;

import org.slf4j.MDC;
import java.util.UUID;

public class CorrelationIdUtil {

    public static String getOrCreateCorrelationId() {
        String correlationId = MDC.get("correlationId");
        if (correlationId == null) {
            correlationId = UUID.randomUUID().toString();
            MDC.put("correlationId", correlationId);
        }
        return correlationId;
    }
}
Enter fullscreen mode Exit fullscreen mode

Update Global Exception Handler

@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {

    String correlationId = CorrelationIdUtil.getOrCreateCorrelationId();

    log.error("Unexpected error occurred. CorrelationId={}", correlationId, ex);

    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(Map.of(
                    "timestamp", LocalDateTime.now(),
                    "message", "Something went wrong. Contact support.",
                    "correlationId", correlationId
            ));
}
Enter fullscreen mode Exit fullscreen mode

🔐 Client Response (Safe + Traceable)

{
  "message": "Something went wrong. Contact support.",
  "correlationId": "7b9e1c9d-3f64-4c9a-9b6f-3f7c8d99a123"
}
Enter fullscreen mode Exit fullscreen mode

Now support teams can debug without leaking sensitive data.


Best Practices

  1. Never return stack traces to clients
    Even in dev environments, this builds bad habits.

  2. Always log exceptions with context
    Use log.error("message", ex) — not just ex.getMessage().

  3. Use generic client messages
    Friendly, non-technical language works best.

  4. Centralize exception handling
    Use @ControllerAdvice to avoid duplication.

  5. Use correlation IDs in production
    Essential for microservices and high-traffic systems.


Conclusion

Exception handling isn’t just about fixing bugs—it’s about security, professionalism, and trust.

By logging full exception details internally and returning safe, controlled responses externally, you protect your application and your users.

Mastering logging exceptions securely in Spring Boot is a must-have skill for anyone serious about Java programming and building real-world APIs.


Call to Action

💬 Have questions about secure logging or exception handling?
👇 Drop them in the comments below!


🔗 Helpful Resources


Enter fullscreen mode Exit fullscreen mode

Top comments (0)