This guide shows how to build an automated audit logging system in Spring Boot using AOP and JPA, enabling seamless tracking of every REST API action—method names, parameters, timestamps, and users—while keeping controllers clean and compliant-ready.This guide shows how to build an automated audit logging system in Spring Boot using AOP and JPA, enabling seamless tracking of every REST API action—method names, parameters, timestamps, and users—while keeping controllers clean and compliant-ready.

Spring Boot Audit Logs: Capture Every API Action Without Writing Boilerplate Code

Audit logging is a crucial part of enterprise applications. Whether you’re building a banking platform, an insurance portal, or an e-commerce API, you must track who did what and when.

In this guide, we’ll build a fully functional Audit Logging system for a Spring Boot REST API. You’ll learn how to capture and persist audit logs automatically for every controller action — without manually adding log statements in each method.

What Is Audit Logging?

Audit logging records what actions were performed in your application, by whom, and when. \n In a REST API, audit logs are useful for:

  • Tracking who created, updated, or deleted resources
  • Investigating issues
  • Maintaining compliance or data integrity

We’ll build this with Spring Boot, JPA, and Aspect-Oriented Programming (AOP).

Real-World Use Case

Imagine a Product Management System where multiple users create, update, and delete products through REST APIs.

\n For compliance and debugging, you need to record:

  • Which user performed the action
  • What API method was called
  • Input parameters
  • Timestamp of the event

Instead of manually logging in every controller, we’ll use Spring AOP (Aspect-Oriented Programming) to intercept and persist audit logs automatically.

Project Structure

Below is the structure of our project:

auditlogging │ ├── src/main/java │ └── com.example.auditlogging │ ├── AuditloggingApplication.java │ ├── aspect/ │ │ └── AuditAspect.java │ ├── config/ │ │ ├── AsyncConfig.java │ │ └── SecurityConfig.java │ ├── controller/ │ │ └── ProductController.java │ ├── entity/ │ │ ├── AuditLog.java │ │ └── Product.java │ ├── filter/ │ │ └── CachingRequestResponseFilter.java │ ├── repository/ │ │ ├── AuditLogRepository.java │ │ └── ProductRepository.java │ └── service/ │ └── AuditService.java │ └── src/main/resources ├── application.properties ├── schema.sql └── data.sql

\

Step 1: Application Entry Point

AuditloggingApplication.java package com.example.auditlogging; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EnableAspectJAutoProxy(proxyTargetClass = true) public class AuditloggingApplication { public static void main(String[] args) { SpringApplication.run(AuditloggingApplication.class, args); } }

Explanation

  • @EnableAspectJAutoProxy enables AOP features in Spring.
  • This class bootstraps the application and loads all beans.

Step 2: Entity Classes

AuditLog.java /** * */ package com.example.auditlogging.entity; /** * */ import jakarta.persistence.*; import java.time.LocalDateTime; @Entity @Table(name = "AUDIT_LOG") public class AuditLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String action; @Column(length = 2000) private String details; private String username; private LocalDateTime timestamp; public AuditLog() {} public AuditLog(String action, String details, String username, LocalDateTime timestamp) { this.action = action; this.details = details; this.username = username; this.timestamp = timestamp; } // Getters & Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public LocalDateTime getTimestamp() { return timestamp; } public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } }

Explanation

  • Represents the AUDIT_LOG table.
  • Stores method name, parameters, user, and timestamp.
  • This table will automatically capture entries whenever an API is called.
  • Columns
  • id: Primary key (auto-generated)
  • action: The controller method name
  • details: Information about the call (arguments, etc.)
  • username: Name of the user who performed the action
  • timestamp: When it happened

Product Entity

Product.java /** * */ package com.example.auditlogging.entity; /** * */ import jakarta.persistence.*; @Entity @Table(name = "product") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String category; private Double price; public Product() {} public Product(String name, String category, Double price) { this.name = name; this.category = category; this.price = price; } // Getters & Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }

Explanation

  • Represents a simple domain entity to perform CRUD operations.
  • The actions performed here will generate audit logs.

Step 3: Repository Layer

AuditLogRepository.java @Repository public interface AuditLogRepository extends JpaRepository<AuditLog, Long> { } ProductRepository.java @Repository public interface ProductRepository extends JpaRepository<Product, Long> {}

Explanation

  • Provides database operations for our entities.
  • Spring Data JPA auto-implements CRUD methods.

Step 4: Controller Layer

ProductController.java import com.example.auditlogging.entity.*; import com.example.auditlogging.repository.*; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/products") public class ProductController { private final ProductRepository productRepository; public ProductController(ProductRepository productRepository) { this.productRepository = productRepository; } @GetMapping public List<Product> getAllProducts() { return productRepository.findAll(); } @PostMapping public Product createProduct(@RequestBody Product product) { return productRepository.save(product); } @PutMapping("/{id}") public Product updateProduct(@PathVariable Long id, @RequestBody Product product) { product.setId(id); return productRepository.save(product); } @DeleteMapping("/{id}") public void deleteProduct(@PathVariable Long id) { productRepository.deleteById(id); } }

Explanation

  • A simple REST controller performing CRUD on Product.
  • Every method is intercepted by our AuditAspect.

This controller exposes four REST endpoints:

  • GET /api/products — fetch all products
  • POST /api/products — create product
  • PUT /api/products/{id} — update product
  • DELETE /api/products/{id} — delete product

Step 5: Aspect Layer — The Core Audit Logic

AuditAspect.java import com.example.auditlogging.entity.*; import com.example.auditlogging.repository.*; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDateTime; import java.util.Arrays; @Aspect @Component public class AuditAspect { private static final Logger logger = LoggerFactory.getLogger(AuditAspect.class); @Autowired private final AuditLogRepository auditLogRepository; public AuditAspect(AuditLogRepository auditLogRepository) { this.auditLogRepository = auditLogRepository; } // Pointcut to capture all controller methods // @Pointcut("within(com.example.auditdemo.controller..*)") @Pointcut("execution(* com.example.auditlogging.controller.ProductController.*(..))") public void controllerMethods() {} // After a successful return from any controller method @AfterReturning(value = "controllerMethods()", returning = "result") public void logAfter(JoinPoint joinPoint, Object result) { try { String method = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); AuditLog log = new AuditLog(); log.setAction(method.toUpperCase()); log.setDetails("Method " + method + " executed with args " + args); log.setTimestamp(LocalDateTime.now()); log.setUsername("system"); auditLogRepository.save(log); // System.out.println("✅ Audit log saved for " + method); logger.info("✅ Audit log saved successfully for method: {}", method); } catch (Exception e) { System.err.println("❌ Error saving audit log: " + e.getMessage()); logger.error("⚠️ Failed to save audit log: {}", e.getMessage()); e.printStackTrace(); } } }

Explanation

  • Uses AspectJ annotations to intercept all controller methods.
  • @AfterReturning runs after a method successfully returns.
  • Builds an AuditLog entry from method name and arguments.
  • Persists the audit log using JPA repository.
  • Prints a log message on success or failure.
  • @Aspect defines this class as an aspect.
  • @Pointcut selects which methods to intercept (here, all controller methods).
  • Saves all audit details to AUDIT_LOG.

Step 6: Asynchronous Configuration

AsyncConfig.java @Configuration @EnableAsync public class AsyncConfig { @Bean(name = "auditExecutor") public Executor auditExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(5); executor.setQueueCapacity(500); executor.setThreadNamePrefix("audit-"); executor.initialize(); return executor; }

Explanation

  • Configures a thread pool for background tasks (audit logging can be made async).
  • Improves performance for high-traffic APIs.

Step 7: Optional — HTTP Request/Response Caching

CachingRequestResponseFilter.java @Component public class CachingRequestResponseFilter implements Filter { public static final String CORRELATION_ID_HEADER = "X-Correlation-Id"; @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); String correlationId = request.getHeader(CORRELATION_ID_HEADER); if (correlationId == null || correlationId.isBlank()) { correlationId = UUID.randomUUID().toString(); } wrappedResponse.setHeader(CORRELATION_ID_HEADER, correlationId); long start = System.currentTimeMillis(); chain.doFilter(wrappedRequest, wrappedResponse); long duration = System.currentTimeMillis() - start; request.setAttribute("audit.correlationId", correlationId); request.setAttribute("audit.durationMs", duration); wrappedResponse.copyBodyToResponse(); } }

Explanation

  • Adds a X-Correlation-Id header for tracing.
  • Measures execution time for each request.
  • Enhances observability when debugging logs.
  • Adds both as request attributes so they can appear in audit details.
  • Returns them in the HTTP response headers.

Step 8: Database Setup

schema.sql CREATE TABLE IF NOT EXISTS audit_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, method VARCHAR(255), endpoint VARCHAR(500), http_method VARCHAR(50), status VARCHAR(255), execution_time_ms BIGINT, timestamp TIMESTAMP ); data.sql -- Insert sample products INSERT INTO product (name, category, price) VALUES ('iPhone 15', 'Electronics', 1299.99); INSERT INTO product (name, category, price) VALUES ('Samsung Galaxy S24', 'Electronics', 1199.50); INSERT INTO product (name, category, price) VALUES ('MacBook Pro 14"', 'Computers', 2499.00); INSERT INTO product (name, category, price) VALUES ('Dell XPS 13', 'Computers', 1399.00); INSERT INTO product (name, category, price) VALUES ('Sony WH-1000XM5', 'Accessories', 399.99); INSERT INTO product (name, category, price) VALUES ('Apple Watch Ultra 2', 'Wearables', 999.00); INSERT INTO product (name, category, price) VALUES ('Logitech MX Master 3S', 'Accessories', 149.99);

Step 9: Application Properties

application.properties spring.datasource.url=jdbc:h2:mem:auditdb spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password= #spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true spring.h2.console.path=/h2-console logging.level.org.springframework.web=INFO spring.security.user.name=admin spring.security.user.password=admin123 # ============= JPA / Hibernate ============= spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.defer-datasource-initialization=true # ============= SQL Initialization ============= spring.sql.init.mode=always

Running the Application

Step 1 — Start the app

Run As → Java Application

Step 2 — Send requests

  • Create a product

curl -X POST http://localhost:8080/api/products \      -H "Content-Type: application/json" \      -d '{"name":"Laptop","category":"Electronics","price":1200}'

  • Fetch all products

curl http://localhost:8080/api/products

  • Update a product

curl -X PUT http://localhost:8080/api/products/1 \      -H "Content-Type: application/json" \      -d '{"name":"Laptop Pro","category":"Electronics","price":1350}'

  • Delete a product

curl -X DELETE http://localhost:8080/api/products/1

Outputs and Explanations

Console Log Output

2025-11-02T23:37:47.372+05:30 INFO 9392 --- [nio-8080-exec-7] c.e.auditlogging.aspect.AuditAspect : ✅ Audit log saved successfully for method: getAllProducts 2025-11-02T23:38:47.591+05:30 INFO 9392 --- [nio-8080-exec-6] c.e.auditlogging.aspect.AuditAspect : ✅ Audit log saved successfully for method: createProduct 2025-11-02T23:39:20.194+05:30 INFO 9392 --- [nio-8080-exec-9] c.e.auditlogging.aspect.AuditAspect : ✅ Audit log saved successfully for method: deleteProduct

Console Log

Explanation

Each line indicates the method name captured by the audit aspect and successful persistence of its record.

Database Table: AUDIT_LOG

| ID | TIMESTAMP | DETAILS | ACTION | USERNAME | |----|----|----|----|----| | 1 | 2025-11-02 23:37:47.27367 | Method getAllProducts executed with args [] | GETALLPRODUCTS | system | | 2 | 2025-11-02 23:38:47.59111 | Method createProduct executed with args [com.example.auditlogging.entity.Product@37a1b59c] | CREATEPRODUCT | system | | 3 | 2025-11-02 23:39:20.194292 | Method deleteProduct executed with args [2] | DELETEPRODUCT | system |

HTTP Response Example

Request

Response

{ "id": 8, "name": "MacBook Air", "category": "Laptop", "price": 1199.99 }

Response Headers

Content-Type: application/json X-Correlation-Id: 8a41dc7e-f3a9-4b78-9f10-8c239e62a4f4

Explanation:

The response returns the saved product details along with the correlation ID generated by the filter.

Audit Entry for Above Request

| ID | TIMESTAMP | DETAIL | ACTION | USERNAME | |----|----|----|----|----| | 1 | 2025-11-02 23:38:47.59111 | Method createProduct executed with args [com.example.auditlogging.entity.Product@37a1b59c] | CREATEPRODUCT | system |

Combined Flow Visualization

| Step | Component | What Happens | Example Output | |----|----|----|----| | 1 | Controller | POST /api/products executes | Product created | | 2 | Aspect | Captures method name + args | CREATEPRODUCT | | 3 | Repository | Saves AuditLog entry | Row inserted in DB | | 4 | Logger | Prints success message | Audit log saved successfully… | | 5 | Filter | Adds correlation ID to response | X-Correlation-Id: |

Final Output Summary

After running all four operations (Create, Read, Update, Delete):

Console Output

Audit log saved successfully for method: createProduct Audit log saved successfully for method: getAllProducts Audit log saved successfully for method: updateProduct Audit log saved successfully for method: deleteProduct

Database

Four rows in AUDIT_LOG table representing each action.

Response Header

Each API response includes X-Correlation-Id.

Response Body Example

{ "id": 1, "name": "Laptop Pro", "category": "Electronics", "price": 1350.0 }

Extending It for Real Users

You can easily integrate with Spring Security to capture the actual logged-in username:

String username = SecurityContextHolder.getContext().getAuthentication().getName(); log.setUsername(username);

Conclusion

You now have a fully working audit logging framework in Spring Boot that automatically captures all REST API actions with minimal code.

This approach ensures:

  • Centralized audit logging for all REST endpoints
  • Non-intrusive — no need to modify each controller
  • Easily extendable to capture IP address, headers, or request body
  • Ready for production with async logging and correlation IDs

This setup ensures every REST API call leaves a clear trace for debugging and compliance purposes.

\

Market Opportunity
Ark of Panda Logo
Ark of Panda Price(AOP)
$0.03512
$0.03512$0.03512
-0.45%
USD
Ark of Panda (AOP) Live Price Chart
Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact service@support.mexc.com for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.

You May Also Like

Is Doge Losing Steam As Traders Choose Pepeto For The Best Crypto Investment?

Is Doge Losing Steam As Traders Choose Pepeto For The Best Crypto Investment?

The post Is Doge Losing Steam As Traders Choose Pepeto For The Best Crypto Investment? appeared on BitcoinEthereumNews.com. Crypto News 17 September 2025 | 17:39 Is dogecoin really fading? As traders hunt the best crypto to buy now and weigh 2025 picks, Dogecoin (DOGE) still owns the meme coin spotlight, yet upside looks capped, today’s Dogecoin price prediction says as much. Attention is shifting to projects that blend culture with real on-chain tools. Buyers searching “best crypto to buy now” want shipped products, audits, and transparent tokenomics. That frames the true matchup: dogecoin vs. Pepeto. Enter Pepeto (PEPETO), an Ethereum-based memecoin with working rails: PepetoSwap, a zero-fee DEX, plus Pepeto Bridge for smooth cross-chain moves. By fusing story with tools people can use now, and speaking directly to crypto presale 2025 demand, Pepeto puts utility, clarity, and distribution in front. In a market where legacy meme coin leaders risk drifting on sentiment, Pepeto’s execution gives it a real seat in the “best crypto to buy now” debate. First, a quick look at why dogecoin may be losing altitude. Dogecoin Price Prediction: Is Doge Really Fading? Remember when dogecoin made crypto feel simple? In 2013, DOGE turned a meme into money and a loose forum into a movement. A decade on, the nonstop momentum has cooled; the backdrop is different, and the market is far more selective. With DOGE circling ~$0.268, the tape reads bearish-to-neutral for the next few weeks: hold the $0.26 shelf on daily closes and expect choppy range-trading toward $0.29–$0.30 where rallies keep stalling; lose $0.26 decisively and momentum often bleeds into $0.245 with risk of a deeper probe toward $0.22–$0.21; reclaim $0.30 on a clean daily close and the downside bias is likely neutralized, opening room for a squeeze into the low-$0.30s. Source: CoinMarketcap / TradingView Beyond the dogecoin price prediction, DOGE still centers on payments and lacks native smart contracts; ZK-proof verification is proposed,…
Share
BitcoinEthereumNews2025/09/18 00:14
US Ranks #1 in CoinGecko Global Meme Coin Interest Report

US Ranks #1 in CoinGecko Global Meme Coin Interest Report

The post US Ranks #1 in CoinGecko Global Meme Coin Interest Report appeared on BitcoinEthereumNews.com. United States ranks #1 in global meme coin interest, capturing
Share
BitcoinEthereumNews2025/12/18 23:49
Franklin Templeton CEO Dismisses 50bps Rate Cut Ahead FOMC

Franklin Templeton CEO Dismisses 50bps Rate Cut Ahead FOMC

The post Franklin Templeton CEO Dismisses 50bps Rate Cut Ahead FOMC appeared on BitcoinEthereumNews.com. Franklin Templeton CEO Jenny Johnson has weighed in on whether the Federal Reserve should make a 25 basis points (bps) Fed rate cut or 50 bps cut. This comes ahead of the Fed decision today at today’s FOMC meeting, with the market pricing in a 25 bps cut. Bitcoin and the broader crypto market are currently trading flat ahead of the rate cut decision. Franklin Templeton CEO Weighs In On Potential FOMC Decision In a CNBC interview, Jenny Johnson said that she expects the Fed to make a 25 bps cut today instead of a 50 bps cut. She acknowledged the jobs data, which suggested that the labor market is weakening. However, she noted that this data is backward-looking, indicating that it doesn’t show the current state of the economy. She alluded to the wage growth, which she remarked is an indication of a robust labor market. She added that retail sales are up and that consumers are still spending, despite inflation being sticky at 3%, which makes a case for why the FOMC should opt against a 50-basis-point Fed rate cut. In line with this, the Franklin Templeton CEO said that she would go with a 25 bps rate cut if she were Jerome Powell. She remarked that the Fed still has the October and December FOMC meetings to make further cuts if the incoming data warrants it. Johnson also asserted that the data show a robust economy. However, she noted that there can’t be an argument for no Fed rate cut since Powell already signaled at Jackson Hole that they were likely to lower interest rates at this meeting due to concerns over a weakening labor market. Notably, her comment comes as experts argue for both sides on why the Fed should make a 25 bps cut or…
Share
BitcoinEthereumNews2025/09/18 00:36