Generic Top

The early-bird price of the new Learn Spring Security OAuth course packages will increase by $50 tomorrow:

>> CHECK OUT THE COURSE

1. Overview

Web applications are often dependent on user input to meet several of their use cases. As a result, form submission is a heavily used mechanism to gather and process data for such apps.

In this tutorial, we'll learn how Spring's flash attributes can help us with the form submission workflow securely and reliably.

2. Flash Attributes Basics

Before we can comfortably use flash attributes, we need to build up a decent level of understanding of the form submission workflow and a few key related concepts.

2.1. Post/Redirect/Get Pattern

A naive way to engineer a web form would be to use a single HTTP POST request that takes care of submission and gives back a confirmation through its response. However, such design exposes the risk of duplicate processing of POST requests, in case the user ends up refreshing the page.

To mitigate the issue of duplicate processing, we can create the workflow as a sequence of interconnected requests in a specific order — namely, POST, REDIRECT, and GET. In short, we call this the Post/Redirect/Get (PRG) pattern for form submission.

On receiving the POST request, the server processes it and then transfers control to make a GET request. Subsequently, the confirmation page displays based on the response of the GET request. Ideally, even if the last GET request gets attempted more than once, there shouldn't be any adverse side-effects.

2.2. Life Cycle of Flash Attributes

To complete the form submission using the PRG pattern, we'll need to transfer information from the initial POST request to the final GET request after redirection.

Unfortunately, we can neither use the RequestAttributes nor the SessionAttributes. That's because the former won't survive a redirection across different controllers, while the latter will last for the entire session even after the form submission is over.

But, we don't need to worry as Spring's web framework provides flash attributes that can fix this exact problem.

Let's see the methods in the RedirectAttributes interface that can help us to use flash attributes in our project:

RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);

RedirectAttributes addFlashAttribute(Object attributeValue);

Map<String, ?> getFlashAttributes();

Flash attributes are short-lived. As such, these are stored temporarily in some underlying storage, just before the redirect. They remain available for the subsequent request after redirect, and then they're gone.

2.3. FlashMap Data Structure

Spring provides an abstract data structure called FlashMap for storing the flash attributes as key-value pairs.

Let's take a look at the definition of the FlashMap class:

public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {

    @Nullable
    private String targetRequestPath;

    private final MultiValueMap<String, String> targetRequestParams 
      = new LinkedMultiValueMap<>(4);

    private long expirationTime = -1;
}

We can notice that the FlashMap class inherits its behavior from the HashMap class. As such, a FlashMap instance can store a key-value mapping of the attributes. Also, we can tie a FlashMap instance to be used only by a specific redirect URL.

Furthermore, every request has two FlashMap instances, namely Input FlashMap and Output FlashMap, which play an important role in the PRG pattern:

  • Output FlashMap is used in the POST request to temporarily save the flash attributes and send them to the next GET request after the redirect
  • Input FlashMap is used in the final GET request to access the read-only flash attributes that were sent by the previous POST request before the redirect

2.4. FlashMapManager and RequestContextUtils

As the name suggests, we can use FlashMapManager to manage the FlashMap instances.

First, let's take a look at the definition of this strategy interface:

public interface FlashMapManager {

    @Nullable
    FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);

    void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}

Simply put, we can say that FlashMapManager allows us to read, update, and save FlashMap instances in some underlying storage.

Next, let's familiarize ourselves with a few static methods available in the RequestContextUtils abstract utility class.

To keep our focus within the scope of this tutorial, we'll limit our coverage to the methods that are relevant to flash attributes:

public static Map<String, ?> getInputFlashMap(HttpServletRequest request);

public static FlashMap getOutputFlashMap(HttpServletRequest request);

public static FlashMapManager getFlashMapManager(HttpServletRequest request);

public static void saveOutputFlashMap(String location, 
  HttpServletRequest request, HttpServletResponse response);

We can use these methods to retrieve the input/output FlashMap instances, get the FlashMapManager for a request, and save a FlashMap instance.

3. Form Submission Use Case

By now, we've established a basic understanding of different concepts around flash attributes. So, let's move further and use them in a poetry contest web application.

Our poetry contest app has a simple use case of accepting poem entries from different poets through the submission of a form. Furthermore, a contest entry will have the necessary information related to a poem, such as a title, a body, and the author's name.

3.1. Thymeleaf Configuration

We'll be using Thymeleaf, which is a Java template engine for creating dynamic web pages through simple HTML templates.

First, we need to add the spring-boot-starter-thymeleaf dependency to our project's pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

Next, we can define some of the Thymeleaf-specific properties in our application.properties file located in the src/main/resources directory:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true 
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

Having defined these properties, we can now create all our views under the /src/main/resources/templates directory. In turn, Spring will append the .html suffix to all the views named inside our controller.

3.2. Domain Model

Up next, let's define our domain model in a Poem class:

public class Poem {
    private String title;
    private String author;
    private String body;
}

Further, we can add the isValidPoem() static method in our Poem class to help us validate that the fields don't allow empty strings:

public static boolean isValidPoem(Poem poem) {
    return poem != null && Strings.isNotBlank(poem.getAuthor()) 
      && Strings.isNotBlank(poem.getBody())
      && Strings.isNotBlank(poem.getTitle());
}

3.3. Create Form

Now, we're ready to create our submission form. For that, we need an endpoint /poem/submit that will serve a GET request to show the form to the user:

@GetMapping("/poem/submit")
public String submitGet(Model model) {
    model.addAttribute("poem", new Poem());
    return "submit";
}

Here, we've used a model as a container to hold the poem-specific data provided by the user. Moreover, the submitGet method returns a view served by the submit view.

Additionally, we want to bind the POST form with the model attribute poem:

<form action="#" method="post" th:action="@{/poem/submit}" th:object="${poem}">
    <!-- form fields for poem title, body, and author -->
</form>

3.4. Post/Redirect/Get Submission Flow

Now, let's enable the POST action for the form. To do that, we'll create the /poem/submit endpoint in the PoemSubmission controller to serve the POST request:

@PostMapping("/poem/submit")
public RedirectView submitPost(
    HttpServletRequest request, 
    @ModelAttribute Poem poem, 
    RedirectAttributes redirectAttributes) {
    if (Poem.isValidPoem(poem)) {
        redirectAttributes.addFlashAttribute("poem", poem);
        return new RedirectView("/poem/success", true);
    } else {
        return new RedirectView("/poem/submit", true);
    }
}

We can notice that if the submission is successful, then control transfers to the /poem/success endpoint. Also, we added the poem data as a flash attribute before initiating the redirect.

Now, we need to show a confirmation page to the user, so let's implement the functionality for the /poem/success endpoint that'll serve the GET request:

@GetMapping("/poem/success")
public String getSuccess(HttpServletRequest request) {
    Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
    if (inputFlashMap != null) {
        Poem poem = (Poem) inputFlashMap.get("poem");
        return "success";
    } else {
        return "redirect:/poem/submit";
    }
}

It's important to note here that we need to validate the FlashMap before we decide to redirect to the success page.

Finally, let's use the flash attribute poem inside our success page to show the title of the poem submitted by the user:

<h1 th:if="${poem}">
    <p th:text="${'You have successfully submitted poem titled - '+ poem?.title}"/>
    Click <a th:href="@{/poem/submit}"> here</a> to submit more.
</h1>

4. Conclusion

In this tutorial, we learned a few concepts around the Post/Redirect/Get pattern and flash attributes. And, we also saw flash attributes in action with a simple form-submission in a Spring Boot web application.

As always, the complete source code for the tutorial is available over on GitHub.

Generic bottom

The early-bird price of the new Learn Spring Security OAuth course packages will increase by $50 tomorrow:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of