What are operation preprocessors in Spring REST Docs, and how do you use them to clean up captured snippets?
answer
- preprocessRequest / preprocessResponse in document()
- prettyPrint, removeHeaders, maskLinks, modifyUris
- affects snippets only, not assertions
- operationPreprocessors().withRequestDefaults / withResponseDefaults
- @AutoConfigureRestDocs(uriHost/uriScheme/uriPort)
basics
~10 sPreprocessors 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 sRaw 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 linesimport 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
Know prettyPrint() exists to make JSON readable in docs.
Name preprocessRequest/preprocessResponse and the common preprocessors (prettyPrint, removeHeaders, maskLinks).
Explain modifyUris for production URLs, default registration to cut duplication, and that preprocessing never affects assertions.
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)