Prevent duplicate form submissions spring MVC

Find out how to prevent multiple form submissions when using spring MVC.

Detailed Video Notes

When building web forms you will need to account for users submitting the form multiple times which could cause unwanted database updates or service request made. Typically users will hit the refresh button, double click a single click button or QAs that machine gun click to try to break something. In this java tutorial we will show a couple of techniques to help prevent duplicate form submission.

Project set up

[0:20]

Creating a project following the spring boot tutorial and spring start initialzer website we will create a project that includes spring-web and velocity as a template engine. We will import the project into eclipse and begin creating the skeleton to our exercise.

Lets create an html5 page that contains a form that will be used to post to a @Controller named DuplicateFormSubmission. It contains web assets such as boostrap strictly for style and jquery which will be used later to bind to an event. By using @ModelAttribute we notify spring to bind the request to a java class named SubmitForm which fields name and emailAddress correlate to the html input attributes. This will be the base of project so lets run the application and access localhost:8080.

Html template

<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>

    <h2>Spring Post/Redirect/Get pattern with FlashMap</h2>

    #if ($formResults)
        <div class="alert alert-success" role="alert">
            Thanks $!formResults.name, updates will be sent to $!formResults.emailAddress. Your confirmation number is $!formResults.confirmationNumber
        </div>
    #end

    <form class="form-inline" name="myform" id="myform" action="/handle" method="post">
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" name="name" id="name">
        </div>

        <div class="form-group">
            <label for="emailAddress">Email</label>
            <input type="email" class="form-control" id="emailAddress" name="emailAddress" placeholder="[email protected]">
        </div>
        <input id="mySubmitButton" type="submit" value="Submit" class="btn btn-default">
    </form>
</body>
</html>

Spring controller

@Controller
public class DuplicateFormSubmission {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String defaultView(HttpServletRequest request, Model model) {
        return "index";
    }
}

Java class form

public class SubmitForm {

    private String name;
    private String emailAddress;
    private int confirmationNumber;

    //.. omitted getters/setters
}

Using RedirectView

[1:5]

Once we have accessed the default request mapping and validated the index.vm is processed it is important before we discuss how spring will handle users action of hitting the refresh button we talk about the the Post / Redirect / Get pattern. When a user makes a successful post instead of returning a web page directly, the POST operation returns a redirection command which will not duplicate form submissions. This way when a user hits the browser refresh button, it will be refresh on the redirection URL and not the post URL.

As you can see the form will post to /handle and we will set up the code that once the post is successful will redirect to the default view. In the process we will generate a random number setting a flash attribute which is a mechanism to temporary store attributes across requests while using a RedirectView which is a helper class to perform a redirection. You could also substitute it with the redirect prefix return "redirect:/"; as well.

So, to put that in words, when a user submits a our sample form it will route to the handlePost method which will generate a random number. Once it is successful it will send a redirect to the browser which will instruct it to make another request to the defaultView method returning the form. The random number will be carried across request and pulled from inputFlashMap which we will place into context for the view to render.

Let's run our example to verify the behavior.

@RequestMapping(value = "/", method = RequestMethod.GET)
public String defaultView(HttpServletRequest request, Model model) {

    Map<String, ?> inputFlashMap = RequestContextUtils
            .getInputFlashMap(request);

    // A RedirectAttributes model is empty when the method is called and is
    // never used unless the method returns a redirect view name or a
    // RedirectView.
    if (inputFlashMap != null) {
        model.addAttribute("formResults", inputFlashMap.get("submitForm"));
    }
    return "index";
}

@RequestMapping(value = "/handle", method = RequestMethod.POST)
public RedirectView handlePost(HttpServletRequest request,
        @ModelAttribute SubmitForm submitForm,
        RedirectAttributes redirectAttrs) {

    Random random = new Random(10);
    submitForm.setConfirmationNumber(random.nextInt(10));

    redirectAttrs.addFlashAttribute(submitForm);

    return new RedirectView("/", true);
}

Disabling submit button

[2:30]

The second approach will be to use JavaScript to disable the submit button. Even though you add JavaScript remember it can be disabled in a browser so it is possible a user could bypass this functionality. Using jquery selector to target find #myform on the page we will bind an event handler when the form is submitted by calling the .submit(). When the form is submitted the button with an id of #mySubmitButton will be disabled and a CSS class will be added. You should visually see the button grey out where you are unable to click it again. Let's run this example and set a break point to see the effects of our code.

<script>
    $("#myform").submit(function( event ) {
        $("#mySubmitButton").prop("disabled", true).addClass("disabled");
    });
</script>

Thanks for joining in today's level up, have a great day!