Apply DAO in Java with focused APIs, clear exception boundaries, and disciplined transaction ownership while avoiding cargo-cult persistence layers.
DAO works well when it is small, intentional, and persistence-focused. It fails when it grows into a vague data layer that owns too much or too little.
Expose methods that reflect application use cases:
findOutstandingInvoices()save(CustomerRecord)deleteExpiredSessions()Avoid generic “do everything” persistence interfaces unless the application truly benefits from them.
Higher layers should not need to reason about every SQLException, ORM exception, or vendor-specific failure type. Wrap or translate those errors into persistence-boundary exceptions that preserve the cause but present a stable interface upward.
Whether you use JDBC, JPA projections, or mappers, keep the mapping near the DAO instead of scattering it across services.
DAO methods should fit the application’s transaction model. They should not silently create unpredictable transactional behavior in random places.
A generic base DAO can look efficient, but it often produces vague APIs and pushes important query meaning back into services.
If the DAO starts deciding discounts, eligibility, or workflow state, it has crossed the persistence boundary.
Returning ORM sessions, lazy proxy assumptions, or storage-specific abstractions from DAO code weakens the layer immediately.
If the DAO layer mirrors the schema mechanically and adds no naming, query concentration, or boundary clarity, it is probably ceremony.
Ask whether the DAO layer makes the codebase easier to change in these ways:
If the answer is no, the layer needs redesign, not defense.
When reviewing DAO quality in Java, ask:
DAO is a good pattern when it clarifies persistence boundaries. It becomes an anti-pattern when it survives only as enterprise nostalgia.