Answer the Question First
In a Thymeleaf + Spring MVC application, the most reliable way to stop accidental duplicate writes is to combine the Post/Redirect/Get (PRG) pattern with an application‑level idempotency token and a database unique constraint. Thymeleaf itself offers no built‑in anti‑resubmission feature; the protection lives in the controller and database.
Why PRG Alone Isn’t Enough
- PRG turns the POST into a 302/303 redirect. After the redirect, a page refresh or back/forward navigation only re‑issues the safe GET, so the mutating POST is not replayed.
- However, if a client retries the original POST before the redirect response arrives (e.g., double‑click, network retry, or a browser’s “resubmit form” dialog), the same write can happen twice.
- Therefore, PRG protects against accidental browser actions but not against deliberate or network‑level retries.
Adding an Idempotency Token
- Generate a unique token (e.g., UUID) when rendering the form and store it in the user’s session.
- Include the token as a hidden field in the form.
- When the POST handler receives the request, atomically check that the token hasn’t been consumed yet. If it’s new, process the write and remove the token from the session.
- If the token is already consumed, return a “duplicate submission” response or redirect to a status page.
This pattern guarantees that even if the same POST is retried, only the first request will be processed.
Database Unique Constraint as the Last Line of Defense
Define a unique key on the natural attributes that identify the record (e.g., email, order number). If a duplicate write slips past the application logic, the database will reject it, and you can translate the error into a user‑friendly message or perform an upsert.
Practical Steps in a Spring MVC Controller
// GET – render form
@GetMapping("/create")
public String showForm(Model model, HttpSession session) {
String token = UUID.randomUUID().toString();
session.setAttribute("formToken", token);
model.addAttribute("formToken", token);
model.addAttribute("entity", new Entity());
return "createForm";
}
// POST – process submission
@PostMapping("/create")
public String submit(@ModelAttribute Entity entity, @RequestParam("formToken") String token, HttpSession session, RedirectAttributes redirectAttrs) {
String sessionToken = (String) session.getAttribute("formToken");
if (sessionToken == null || !sessionToken.equals(token)) {
return "redirect:/duplicate"; // or show error
}
session.removeAttribute("formToken"); // consume token atomically
entityService.save(entity); // DAO layer enforces unique constraint
redirectAttrs.addFlashAttribute("msg", "Created successfully");
return "redirect:/success";
}
Summary
- PRG protects against accidental browser refreshes and navigation.
- Idempotency token blocks client‑side retries and double‑clicks.
- Unique DB constraint protects against any remaining race conditions or malicious duplicates.
- Thymeleaf only renders the form; it does not provide anti‑resubmission logic.
Missing Diagnostic Detail?
If you’re experiencing duplicates even after implementing PRG, it may be due to a race condition in token consumption or a missing unique constraint. Verify that the token is removed atomically and that the database schema includes a unique index on the relevant columns.