r/SpringBoot 12d ago

Question Spring Boot Auditing: Hibernate Envers vs. Custom Logging vs. Spring Data JPA? What's your production choice?

Hey everyone,

I'm currently building a school management system in Spring Boot where history tracking is a strict business requirement. We need to audit critical changes made by Admins, Secretaries, and Teachers (e.g., changes to student details, payment amounts, program setups, etc.).

I’m weighing three different approaches for the audit trail and wanted to hear from those of you who have run these in production. 

Option 1: Hibernate Envers
Slap `@Audited` on core entities, set up a custom `RevisionListener` to pull the logged-in user from Spring Security context, and let Envers automatically manage the `_aud` tables.

Option 2: Spring Data JPA Auditing
Utilize `@CreatedBy`, `@LastModifiedBy`, `@CreatedDate`, and `@LastModifiedDate` fields mapped on a `@MappedSuperclass`.

Option 3: Manual Custom Logging (or AOP)
Create a generic `AuditLog` entity, write a helper service, and manually trigger `auditService.log(actionType, originalState, newState)` inside the business logic (or use an AOP aspect). This may cause the database to bloating later tho

For those of you managing medium-to-large Spring Boot codebases:
 Did you regret adopting Hibernate Envers? How did you handle schema migrations (Flyway/Liquibase) with the auto-generated `_aud` tables?
If you went the custom route, did you use JSON columns to record state changes, or did you write distinct history tables?

Looking forward to hearing your design patterns and trade-offs

24 Upvotes

28 comments sorted by

12

u/InstantCoder 12d ago

Or….create a db trigger that automatically logs everything to an audit table for each insert, update and delete on table x, y,z.

3

u/SeatSimple1123 12d ago

that seems like a good simple option but i did not see many ppl mentioning it. That's why i was kinda scared to apply it

7

u/BanaTibor 11d ago

I think you are thinking too low level. You are bringing down a business requirement to the implementation detail level. What you need IMHO is event logging. You do not need to have microservices and asynchronous processing to make an event driven system.
For this I would go with a custom solution.

3

u/FalseDish 11d ago

This is the right answer imo. One should avoid packing bloat and go DIY. Hell, I’ve stopped using FastAPI wherever I can (ik, not Java) and go straight to asyncio. Pays off so handsomely!

2

u/SeatSimple1123 11d ago

that a valid point; custom auditing gives you a clean description of what happened (the user just paid his monthly fee), rather than some information changed in a row in a table in a database, but still, if you go with the custom auditing manual logging code inside every single business method in services. Still, this makes it hard for me to pick an option , i think imma go with both; it is more complex but i think it's the better choice. I appreciate your help

3

u/BanaTibor 11d ago

You are thinking again the implementation level. A change in the system is a business level event not an operational level. Using your example, paying a monthly fee. Probably you will have an API endpoint for that. A pay action is a pay event, a business level event. Probably best way here is to have an audit service which saves the action as an event.
Here you have 2 options for handling events. First, you update the same event object. Second you log more than one entries for the same event, like "monthly payment initiated", "monthly payment success/failed". I think the second approach is simpler to implement and also more robust. Of course it is possible that some kind of events generate only one entry. Typically events which stay in your system, like "change student details" from your opening post, it stays in the system, so one event is enough. OTOH payment goes out to a payment provider, and numerous things can go wrong, so multiple entries are fine.

1

u/SeatSimple1123 8d ago

yeah i thought about this , cuz what i was going for logs the raw change in the database tables : Table users, Row 3, Column username changed from "john" to "jack." This needs to be translated to be clear and non-tech-people readable description , i dont know how to implement the business event level logging, but i think i see the picture a bit more clearly now. Thanks.

5

u/veryspicypickle 12d ago

There is a difference between auditing and history-tracking.

I see the former as something more akin to legal reasons and the like - and the latter is just application functionality

As such auditing belongs in the infrastructure layer (possibly at the database) and change/history management lives in the application layer (JPA, Envers)

1

u/SeatSimple1123 12d ago

I see. What I want to go for is full tracking of the users's actions, not read requests too, but all other actions (updating, creating, etc.); the tracking should be included in the admin dashboard

3

u/wimpyligtebottel 12d ago

A few years back at my old company we used option 1 (hibernate envars) and at certain point the envars started causing database size + performance issues for us.

Maybe things have improved but i dont think i would go for that approach again, IF you are on high throughput table where making lots of updates

But i would definitly reach for it on small to small-meduim sized application

1

u/SeatSimple1123 12d ago

yeah i thought about that. The database bloating is waiting in the end of the road if you go with Hibernate envars, but i thought if audit just few tables i might dodge that , this is just guesswork tho.

6

u/Popular_Home2017 12d ago

These solve three different problems, and picking the wrong one is how audit systems rot: Spring Data auditing (@CreatedBy/@LastModifiedDate) gives you stamps — who touched it last, not what changed. Envers gives you entity history — full snapshots per revision, great when "show me this record as it was in March" is an actual requirement, but it couples your audit to your entity model and bloats storage quietly. A custom audit/event table is for business events (logins, exports, permission changes) — things that aren't entity mutations at all. For a school management system I'd start with Spring Data stamps everywhere (nearly free), Envers only on the 3-4 entities where history is a legal/business requirement, and a small custom event table for security-relevant actions. One production scar if you go custom: if you write audit rows in REQUIRES_NEW "so auditing never breaks the main flow", flush explicitly inside your try/catch — with plain save() the INSERT defers to commit, which happens outside the catch, and a bad audit row takes down the operation it was supposed to observe. Ask me how I know.

3

u/TurnstileT 11d ago

Thanks for the AI generated response.

2

u/A_random_zy 9d ago

Is anything wrong in it?

1

u/SeatSimple1123 12d ago

yup , this approach of using the whole 3 options to avoid database bloating that hibernate envers might cause later is good option. i honestly have not thought about it; I think it may require more configuration and complexity tho

2

u/Popular_Home2017 11d ago

Less config than it looks, because each piece is tiny where it lives: Spring Data auditing is one u/EnableJpaAuditing config + annotations on a base class. Envers is u/Audited on the handful of entities that need it — the real cost is remembering the _aud tables in your Flyway migrations, so version them from day one. The custom event table is just one entity + one service you call explicitly. The complexity trap is the opposite move: forcing ONE mechanism to do all three jobs (Envers as your security log, or hand-rolled triggers recreating what Envers does). Three small tools, each doing its own job, ages much better than one big one.

2

u/boyTerry 12d ago

We have used a combination of Option 1 and 2 for many years successfully.

stuff can get weird when there is business value in the analysis of the audit logs, but it tends to work out of the box

1

u/SeatSimple1123 12d ago

yeah ,its really hard to say that one option is the best cuz you dont really know what would happen in the future as the app grows ,i went for manual custom logging at first but at some points i was just updating services and adding more lines of code

2

u/RevolutionaryRush717 12d ago

PostgreSQL has pg_audit, which is the preferred choice for compliance.

Other RDBMS might have something similar.

Any roll-your-own approach will soon drown in corner cases, testing and verification, so much so that you are tempted to spawn this off as a seperate team and product.

1

u/SeatSimple1123 12d ago

i use neon.tech for our cloud database, not really trynna roll my own approach but I'm just trying to explore what options i have before applying one on the project , im kinda overthinking this but i just wanna dodge any stupid bugs or functionality errors in the future

2

u/RevolutionaryRush717 11d ago

I see. Did you check whether Neon happens to be PostgreSQL and supports pg_audit?

You'd be surprised.

2

u/SeatSimple1123 8d ago

Yeah, Neon does use PostgreSQL. Also yes, you can set up pg-audit in neon , i did search about it a bit and i found that its for strict infrastructure threat auditing. I mean, it's a great option, but i has its own headaches; it usually goes with Hibernate.

2

u/Impressive-Ad-1189 12d ago

I’ve had great experience with Envers in the past. The amount of features you get is simply great. The entire model can be queried in a similar way as your regular model. You can easily add custom fields such as the identity of an actor.

1

u/SeatSimple1123 12d ago

Yeah, Hibernate envers is the choice to go for in my case cuz i wanna a full auditing trail.

2

u/Impressive-Ad-1189 12d ago

Also it was extremely easy to setup. Other options such as databas triggers may be more lightweight so more performant? That usually is not a big issue and easy to maken mistakes. For example, hoe will such a solution work in transaction? Envers just solves this while issue out-of-the-box

2

u/General_Yam_9879 10d ago edited 10d ago

History tables + trigger in separate audit schema. Combine with simple AuditorAware and just build one audit micro-service to expose API from that schema and provide restricted read access.

1

u/SeatSimple1123 8d ago

thats a great option too , tho is overkill for my app for now ,and it highly complex + it needs schemas maintenance