eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – All Access – NPI EA (cat= Spring)
announcement - icon

All Access is finally out, with all of my Spring courses. Learn JUnit is out as well, and Learn Maven is coming fast. And, of course, quite a bit more affordable. Finally.

>> GET THE COURSE
Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Introduction

Form-encoded endpoints are common in login and subscription flows, but easy to misunderstand without precise request documentation. API consumers need to know which parameters are required, which are optional, which values are valid, and which extra parameters are intentionally ignored.

In this tutorial, we’ll document form parameters for a Spring MVC endpoint by using Spring REST Docs and MockMvc tests.

It’s important to note that although related, query parameters serve different use cases and are documented with different REST Docs snippets, so we won’t cover them here.

2. Why Document Form Parameters?

In form submissions, everything starts as key-value pairs. Without documentation, there is room for ambiguity:

  • Are all fields required?
  • Which values do constrained fields accept?
  • Are there ignorable parameters?

Spring MVC handles all this for us, but API consumers still need exact details. Spring REST Docs generates documentation snippets after a the test being documenteded passes.

It’s important to note that REST Docs doesn’t convert arbitrary assertions into documentation automatically. We still describe each parameter programmatically. The benefit we get is that we get the documentation only from the tests that pass and validate the behavior of the code.

Note that we’ll handle only form parameters in this article, not the query parameters:

  • Form parameters belong to a request body encoded as application/x-www-form-urlencoded
  • Query parameters are part of the URL, for example /newsletter/subscriptions?frequency=weekly. They serve different use cases and are documented with different REST Docs snippets.

3. Building a Form-Based Endpoint

Let’s build a small newsletter subscription API that receives form fields, validates the payload, and returns a JSON response.

Basically, our form model represents request fields from application/x-www-form-urlencoded data. Spring binds parameters to this object by matching parameter names to JavaBean property names.

So, let’s start with our subscription form. We use Bean Validation annotations to enforce required and constrained values:

public class SubscriptionForm {

    @Email
    @NotBlank
    private String email;

    @NotBlank
    private String name;

    @NotBlank
    @Pattern(regexp = "weekly|monthly")
    private String frequency;

    private List<String> topics;
    private boolean marketingAccepted;

    // standard getters and setters
}

The pattern for frequency explicitly states valid values. If a client sends a value that isn’t weekly or monthly, validation will fail before business logic runs. Note that Spring REST Docs doesn’t infer that constraint, so it doesn’t automatically add valid values to the resulting documentation.

4. Test Setup and Request Construction

The main idea of REST Docs is generating snippets from tested behavior. So, we use MockMvc with REST Docs configuration to create a request that includes all supported form fields.

4.1. Test Setup and Snippet Naming

Our test class uses @WebMvcTest, and we also need to specify the RestDocumentationExtension so JUnit can manage the RestDocumentationContext from Spring REST Docs:

@WebMvcTest
@ExtendWith(RestDocumentationExtension.class)
class NewsletterControllerDocumentationTest {

    @Autowired
    private MockMvc mockMvc;

    @BeforeEach
    void setUp(
      WebApplicationContext context, RestDocumentationContextProvider restDocumentation) {
        this.mockMvc = webAppContextSetup(context)
          .apply(documentationConfiguration(restDocumentation))
          .alwaysDo(document("{method-name}"))
          .build();
    }
}

Our setUp() method prepares the context for each test:

  • apply(documentationConfiguration(restDocumentation)) attaches Spring REST Docs to the MockMvc instance built by webAppContextSetup(), so it captures requests and responses.
  • alwaysDo(document(“{method-name}”)) adds a default documentation generation action that runs for every mockMvc.perform() in our test. The {method-name} placeholder captures the current test method name.

When we want a fixed snippet directory name, we set an explicit identifier in the test itself, like document(“newsletter-subscribe”). That creates snippets under target/generated-snippets/newsletter-subscribe.

4.2. The Form Request

Let’s break down each part of the test, starting with a simple mock payload that includes the form fields. We use the param() method to send key-value pairs to an endpoint that accepts form payloads:

private MockHttpServletRequestBuilder postSubscription() {
    return post("/newsletter/subscriptions")
      .contentType(MediaType.APPLICATION_FORM_URLENCODED)
      .param("email", "[email protected]")
      .param("name", "Baeldung API")
      .param("frequency", "weekly")
      .param("topics", "spring", "testing")
      .param("marketingAccepted", "true");
}

Note that we send topics twice with param(“topics”, “spring”, “testing”), which is how it handles multi-value form fields.

5. Documenting Form Parameters

Now, we’re ready for documentation.

5.1. Describing Form Parameters

We use formParameters() to document the form payload. We define a name and a description, using optional() for non-required parameters:

private FormParametersSnippet docFormParams() {
    return formParameters(
      parameterWithName("email").description("The subscriber email address"),
      parameterWithName("name").description("The display name of the subscriber"),
      parameterWithName("frequency").description("Delivery frequency: weekly or monthly"),
      parameterWithName("topics").optional()
        .description("One or more selected topic values"),
      parameterWithName("marketingAccepted").optional()
        .description("Whether marketing messages are accepted"));
}

If valid frequency values change, we need to remember to manually update the parameter description, as the @Pattern annotation doesn’t infer it.

Most importantly, optional() controls how Spring REST Docs documents a parameter. It doesn’t affect Bean Validation. Constraints such as @NotBlank apply independently when Spring validates the form object.

If we add or remove a required parameter, one of two things usually happens: either request validation behavior changes and the test fails, or descriptor matching fails because our documented parameters no longer match the request.

5.2. Dealing With Bean Validation Constraints

To avoid describing constraints manually, we can read validation metadata through ConstraintDescriptions and incorporate it into parameter descriptions by calling descriptionsForProperty().

Let’s create a helper method to get the constraint description for the SubscriptionForm class and frequency property:

private String constraintsFor(String property) {
    ConstraintDescriptions constraints = new ConstraintDescriptions(SubscriptionForm.class);
    List<String> frequencyConstraints = constraints.descriptionsForProperty("frequency");
}

Now, we can go back to docFormParams() and use our constraintsFor() method:

parameterWithName("frequency")
  .description("Delivery frequency. Constraints: " + constraintsFor("frequency")),

This is the description we get:

Delivery frequency. Constraints: Must match the regular expression `weekly|monthly`, Must not be blank

So, it includes descriptions for all validation annotations. If they change, the description changes, too.

5.3. Defining Ignored Parameters

Some clients send extra form values that we don’t want to include in our API documentation. For example, frontend tracking parameters are irrelevant to business logic. By default, REST Docs fails with a SnippetException if it finds any undocumented parameter, so we have to ignore them explicitly.

Let’s start by going back to postSubscription() and sending the trackingId parameter that we’ll ignore later:

private MockHttpServletRequestBuilder postSubscription() {
    return post("/newsletter/subscriptions")
      // previous parameters ...
      .param("trackingId", "campaign-42");
}

Now, in docFormParams(), we add trackingId as an ignored parameter:

private FormParametersSnippet docFormParams() {
    return formParameters(
      // earlier parameters ...
      parameterWithName("trackingId").ignored());
}

This way, the parameter can appear, but it isn’t part of the API docs we get in the end.

6. Executing the Test and Generating Snippets

Finally, let’s put all those parts together in a mock request:

@Test
void whenFormRequestIsValid_thenDocumentFormParameters() throws Exception {
    mockMvc.perform(postSubscription())
      .andExpect(status().isCreated())
      .andExpect(jsonPath("$.id").isNumber())
      .andDo(
        document("newsletter-subscribe", 
          docFormParams()));
}

This single test validates the endpoint and defines field-level documentation for all form parameters. Most importantly, it helps keep the contract in one place.

When we run our documented test, Spring REST Docs writes snippet files under target/generated-snippets/newsletter-subscribe, which was the name we chose with the document() method.

6.1. Generated Form Snippet

The generated snippet for our form POST test execution shows the parameters in a structured format, using the descriptions we provided with the parameterWithName() calls:

|===
|Parameter|Description

|`+email+`
|The subscriber email address

|`+name+`
|The display name of the subscriber

|`+frequency+`
|Delivery frequency: weekly or monthly

|`+topics+`
|One or more selected topic values

|`+marketingAccepted+`
|Whether marketing messages are accepted

|===

We can include this snippet in AsciiDoc pages during the Maven build using the Asciidoctor plugin.

The main advantage is that our snippets come from requests that actually ran in tests. If tested behavior or descriptors no longer match, tests or snippet generation fail, forcing us to update code and documentation together.

7. Strict vs Relaxed Parameter Documentation

By default, formParameters(…) is strict. This means undocumented parameters fail the snippet unless we mark them as ignored.

However, sometimes we want lighter constraints. For example, we may only care about documenting core fields while allowing additional keys in the request. In that case, we enter the relaxed mode via relaxedFormParameters() instead of formParameters(). Let’s write a new helper method with relaxedFormParameters():

private FormParametersSnippet docRelaxedCoreFormParams() {
    return relaxedFormParameters(
      parameterWithName("email").description("The subscriber email address"),
      parameterWithName("name").description("The display name of the subscriber"),
      parameterWithName("frequency").description("Delivery frequency: weekly or monthly"));
}

Here’s the accompanying test method:

@Test
void whenOnlyCoreFieldsMatter_thenDocumentWithRelaxedMode() throws Exception {
    mockMvc.perform(postSubscription())
      .andExpect(status().isCreated())
      .andDo(document("newsletter-subscribe-relaxed", docRelaxedCoreFormParams()));
}

It submits the form, checks if the return status is created, then generates the documentation snippets in the newsletter-subscribe-relaxed folder.

7.1. Quick Reference for Parameter Modes

Let’s review all parameter processing modes:

Mode How to use What happens
Required parameter parameterWithName(“email”) Must be present in the documented request
Optional parameter parameterWithName(“topics”).optional() We can omit it without snippet failure
Ignored parameter parameterWithName(“trackingId”).ignored() May be present but is excluded from published contract
Relaxed mode relaxedFormParameters(…) Tolerates undocumented parameters

In practice, we’ll use strict mode for stable contracts, add ignored() for known non-contract inputs, and switch to the relaxed mode only when we need additional flexibility.

8. Conclusion

In this article, we documented a form-encoded Spring MVC endpoint using Spring REST Docs and a test-first workflow. With Spring REST Docs, we document tested behavior and regenerate snippets as part of the test cycle, which helps avoid documentation drift over time.

We created a dedicated form model, validated input fields, and exposed a POST endpoint that consumes application/x-www-form-urlencoded requests. In the end, we used formParameters() to describe each key, including optional and ignored fields.

We covered required parameters, optional parameters, ignored parameters, and differences between strict and relaxed mode. Also, we saw how to define where REST Docs saves the documentation snippets.

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

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
guest
0 Comments
Oldest
Newest