skip to content

What are operation preprocessors in Spring REST Docs, and how do you use them to clean up captured snippets?

level: seniorimportance: should knowfreq 38%

answer

  1. preprocessRequest / preprocessResponse in document()
  2. prettyPrint, removeHeaders, maskLinks, modifyUris
  3. affects snippets only, not assertions
  4. operationPreprocessors().withRequestDefaults / withResponseDefaults
  5. @AutoConfigureRestDocs(uriHost/uriScheme/uriPort)

basics

~10 s

Preprocessors transform the captured request/response before snippets are written — e.g. Preprocessors.prettyPrint() to format JSON, removeHeaders(...) to drop noise, maskLinks() to hide URIs. You attach them with document(id, preprocessRequest(...), preprocessResponse(...), snippets...).

solid answer

~30 s

Raw MockMvc output is ugly: single-line JSON, host/content-length headers, verbose links. Operation preprocessors run over the captured operation before snippet rendering. You wrap them with OperationRequestPreprocessor/OperationResponsePreprocessor via document(identifier, Preprocessors.preprocessRequest(...), Preprocessors.preprocessResponse(...), snippets...). Common ones from org.springframework.restdocs.operation.preprocess.Preprocessors: prettyPrint() (formats JSON/XML bodies), removeHeaders("Host", "Content-Length"), removeMatchingHeaders(regex), maskLinks() (replaces hrefs with "..."), modifyUris() (rewrite scheme/host/port so docs show the production URL not localhost), modifyHeaders(), and replacePattern(). To avoid repeating them on every test, register defaults in the RestDocumentationConfigurer at MockMvc build time (operationPreprocessors().withRequestDefaults(...).withResponseDefaults(...)), or via @AutoConfigureRestDocs(uriHost/uriPort). Preprocessing only affects rendered snippets, never your assertions.

code

java · 22 lines
java
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;

mockMvc.perform(get("/users/{id}", 1).accept(MediaType.APPLICATION_JSON))
    .andExpect(status().isOk())
    .andDo(document("users-get",
        preprocessRequest(
            modifyUris().scheme("https").host("api.example.com").removePort(),
            removeHeaders("Host", "Content-Length")),
        preprocessResponse(
            prettyPrint(),
            maskLinks(),
            removeMatchingHeaders("Vary|X-.*")),
        responseFields(
            fieldWithPath("id").description("Unique id"),
            fieldWithPath("name").description("Display name"))));

// Registering defaults once (manual MockMvc setup):
// .apply(documentationConfiguration(restDocs)
//     .operationPreprocessors()
//     .withRequestDefaults(removeHeaders("Host"))
//     .withResponseDefaults(prettyPrint()));

go deeper

for a junior

Know prettyPrint() exists to make JSON readable in docs.

for a middle

Name preprocessRequest/preprocessResponse and the common preprocessors (prettyPrint, removeHeaders, maskLinks).

for a senior

Explain modifyUris for production URLs, default registration to cut duplication, and that preprocessing never affects assertions.

for a principal

Standardize a preprocessor policy (URI rewrite, header allowlist, secret masking) across the team via shared defaults/customizers.

**Why preprocessors exist.** What MockMvc captures is literal wire data: bodies serialized on one line, headers like `Host`, `Content-Length`, `Content-Type` with charset noise, and `localhost:8080` URIs. Publishing that verbatim is unreadable and leaks test-environment details. Preprocessors are a transformation pipeline applied to the captured **operation** (request or response) *after* the test runs but *before* the snippet templates render — so they change documentation only, never the assertions your test made. **Where they attach.** `org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document(String, OperationRequestPreprocessor, OperationResponsePreprocessor, Snippet...)`. You build the preprocessors with static factories on `org.springframework.restdocs.operation.preprocess.Preprocessors`: - `preprocessRequest(OperationPreprocessor...)` - `preprocessResponse(OperationPreprocessor...)` **The built-in OperationPreprocessors.** - `prettyPrint()` — reformats JSON/XML bodies with indentation. - `removeHeaders(String...)` — drops named headers (e.g. `Host`, `Content-Length`). - `removeMatchingHeaders(String regex)` — drops headers by pattern. - `maskLinks()` / `maskLinks(String mask)` — replaces hypermedia `href` values (default mask `...`), so docs don't pin volatile URLs. - `modifyUris()` — a builder to rewrite `scheme(...)`, `host(...)`, `port(...)`; used to make docs show `https://api.example.com` instead of `http://localhost:8080`. (In WebTestClient/REST Assured the base URI is set differently, but modifyUris works across all.) - `modifyHeaders()` — a builder to `set(...)`, `remove(...)`, `removeMatching(...)` headers. - `replacePattern(Pattern, String)` — regex replace anywhere in the body (mask tokens, timestamps). **Reducing duplication — defaults.** Repeating preprocessors on every `document(...)` is noise. Two approaches: 1. **Configurer defaults** at MockMvc build time: `.apply(documentationConfiguration(restDocumentation).operationPreprocessors().withRequestDefaults(removeHeaders("Host")).withResponseDefaults(prettyPrint()))`. Then `document("id", snippets...)` inherits them. 2. With Boot slice tests you customize via a `RestDocsMockMvcConfigurationCustomizer` bean or `@AutoConfigureRestDocs(uriScheme="https", uriHost="api.example.com", uriPort=443)` to fix the documented URIs globally. **Order matters.** Preprocessors run left-to-right in the order supplied; e.g. mask before pretty-printing if the mask changes body structure. **Gotchas.** - Preprocessing never affects test correctness — you still assert on the real response. - `modifyUris()` rewrites the *documented* host; the actual request still hits MockMvc's dispatcher. - Pretty-printing a non-JSON body is a no-op; it keys off content type. - If you register defaults *and* pass per-call preprocessors, the per-call ones replace (not merge with) defaults for that document call — be deliberate. **When to use.** Always add at least `prettyPrint()` on responses and `removeHeaders` for noise on any docs you'll publish; use `modifyUris`/`maskLinks` for public API docs so environment specifics don't leak.

  • Do preprocessors change what your test asserts?
    No. They only transform the captured operation used to render snippets. Your andExpect assertions run against the real, unmodified MockMvc response; preprocessing is purely a documentation concern.
  • How do you avoid repeating prettyPrint() and removeHeaders on every document() call?
    Register them as defaults: in manual setup via operationPreprocessors().withRequestDefaults(...)/withResponseDefaults(...) on the documentationConfiguration; with Boot, a RestDocsMockMvcConfigurationCustomizer bean or @AutoConfigureRestDocs URI attributes. Per-call preprocessors then replace those defaults for that call.

saying these in an interview costs you the question

  • Thinking preprocessors alter the response your assertions see
  • Not knowing modifyUris/uriHost is how you replace localhost:8080 in published docs
  • Believing per-call preprocessors merge with configured defaults (they replace them)

context