LocaleResolver and switching languages, @SessionAttributes and @CookieValue (and their security), testing with MockMvc and Mockito, integration testing, file uploads end to end, JPA/WebSocket integration, high-traffic features, caching, async processing and horizontal scaling.
Published September 25, 2026
This lesson collects the "rounding out" Spring MVC questions: internationalisation, sessions and cookies, testing, uploads, and performance. For the scaling questions, show that you understand statelessness. It's the idea everything else depends on.
LocaleResolver?Short answer: A LocaleResolver determines the locale of each request. MessageSource then uses it to pick translated messages (messages_hi.properties, messages_fr.properties), and formatters use it for dates and numbers.
| Resolver | Where the locale comes from |
|---|---|
AcceptHeaderLocaleResolver (Boot default) | The browser's Accept-Language header |
SessionLocaleResolver | Stored in the HTTP session |
CookieLocaleResolver | Stored in a cookie |
FixedLocaleResolver | Always the same locale |
Short answer: Use a LocaleChangeInterceptor, which reads a request parameter such as ?lang=hi, together with a resolver that can store the choice (session or cookie). A language dropdown then just links to the current page with ?lang=….
@Configuration
class I18nConfig implements WebMvcConfigurer {
@Bean LocaleResolver localeResolver() {
CookieLocaleResolver r = new CookieLocaleResolver("lang");
r.setDefaultLocale(Locale.ENGLISH);
return r;
}
@Override public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang"); // /products?lang=hi
registry.addInterceptor(lci);
}
}
// templates: <h1 th:text="#{products.title}">…</h1> → looked up in messages_hi.properties
Key points to cover:
Accept-Language for server-generated messages and validation errors.@SessionAttributes and @CookieValue for?Short answer:
@SessionAttributes("wizardForm"), on a controller class, stores the named model attributes in the HTTP session across requests. It's typical for multi-step forms and wizards. Call SessionStatus.setComplete() at the end to clear them.@CookieValue("theme") binds a cookie's value to a handler parameter.@Controller
@SessionAttributes("checkout")
class CheckoutWizard {
@PostMapping("/checkout/address") String address(@ModelAttribute("checkout") CheckoutForm f) { return "redirect:/checkout/payment"; }
@PostMapping("/checkout/confirm") String confirm(@ModelAttribute("checkout") CheckoutForm f, SessionStatus status) {
orders.place(f);
status.setComplete(); // remove it from the session
return "redirect:/orders";
}
@GetMapping("/")
String home(@CookieValue(name = "theme", defaultValue = "light") String theme) { … }
}
@SessionAttributes and @CookieValue?Short answer:
HttpOnly, Secure and SameSite flags. Don't put sensitive data in cookies unless it's encrypted.Short answer:
@WebMvcTest and MockMvc, which performs requests without a real server and checks the status, headers, JSON and views.@SpringBootTest, using MockMvc, TestRestTemplate or WebTestClient.@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired MockMvc mvc;
@MockitoBean ProductService service;
@Test void rejectsInvalidProduct() throws Exception {
mvc.perform(post("/api/v1/products").contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"\", \"price\": -5}"))
.andExpect(status().isBadRequest());
verifyNoInteractions(service);
}
}
Short answer:
MockMvc, @WebMvcTest, @SpringBootTest, test slices.@WithMockUser).Short answer: In a pure unit test, create the controller with new, passing Mockito mocks. In a web-slice test, @WebMvcTest loads only the MVC components, and @MockitoBean (Boot 3.4+, formerly @MockBean) replaces each service bean in the context with a mock that you stub.
Key points to cover:
Short answer:
@SpringBootTest sparingly, for real end-to-end flows.@ServiceConnection) instead of in-memory substitutes.Short answer: Multipart requests (enctype="multipart/form-data") are parsed by a MultipartResolver. Spring Boot auto-configures StandardServletMultipartResolver, which uses the Servlet API's built-in multipart support. Each uploaded file arrives as a MultipartFile, giving you its name, size and content type, an InputStream, and transferTo.
Short answer: In Spring Boot, multipart support is on by default. Set the limits:
spring:
servlet:
multipart:
max-file-size: 10MB # per file (default 1MB)
max-request-size: 25MB # whole request (default 10MB)
The HTML form needs method="post" and enctype="multipart/form-data". In classic Spring MVC, register a MultipartResolver bean named multipartResolver, and configure the servlet's MultipartConfigElement.
Short answer: Take a MultipartFile parameter with @RequestParam (or @RequestPart for mixed JSON-plus-file requests). Validate it, then stream it to storage.
@PostMapping(value = "/documents", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<DocumentDto> upload(@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) throw new BadRequestException("empty file");
if (!Set.of("application/pdf", "image/png").contains(file.getContentType()))
throw new BadRequestException("unsupported type");
String key = UUID.randomUUID() + ".bin"; // never trust the client's file name as a path
try (InputStream in = file.getInputStream()) {
storage.put(key, in, file.getSize()); // e.g. S3 or blob storage
}
return ResponseEntity.status(HttpStatus.CREATED).body(new DocumentDto(key, file.getOriginalFilename()));
}
Short answer:
| Problem | Solution |
|---|---|
Files exceed the limits (MaxUploadSizeExceededException) | Set max-file-size and max-request-size; handle the exception with a clear 413 response |
| Wrong or dangerous file types | Check the extension and the actual content (magic bytes, for example with Apache Tika); scan for malware |
Path traversal (a file name containing .. segments that climbs out of the upload folder) | Generate your own storage names; never build paths from getOriginalFilename() |
| Slow or huge uploads tying up app threads | Upload directly to object storage with pre-signed URLs; raise timeouts only for the upload endpoints |
| Memory pressure | Stream (getInputStream) instead of getBytes(); set file-size-threshold so large files spill to disk |
Short answer:
@Transactional in the service layer. Keep entities out of the web layer by mapping them to DTOs.@EnableWebSocketMessageBroker with STOMP (@MessageMapping handlers, SimpMessagingTemplate to push messages to subscribers), or raw handlers with @EnableWebSocket. Server-Sent Events (SseEmitter) are a simpler option for one-way pushes.Short answer:
@Cacheable and HTTP caching (ETags).spring.threads.virtual.enabled=true, Java 21+), which make blocking code scale cheaply.Short answer: Enable Spring's cache abstraction with @EnableCaching, annotate service methods with @Cacheable, and evict with @CacheEvict (or update with @CachePut) when the data changes. Plug in a provider: Caffeine for local caching, Redis for a cache shared across instances.
@Cacheable(cacheNames = "products", key = "#id")
public ProductDto find(long id) { return mapper.toDto(repository.findById(id).orElseThrow()); }
@CacheEvict(cacheNames = "products", key = "#id")
public void update(long id, UpdateProduct cmd) { … }
Key points to cover:
ResponseEntity.ok().eTag(...) and Cache-Control headers, so clients and CDNs avoid repeat requests entirely.Learn it in depth → Caching Strategies
Short answer: A handler can return a type that lets the container thread go back to the pool while the work finishes elsewhere:
Callable<T>: Spring runs it on a task executor.DeferredResult<T>: completed later by another thread, for example when a message arrives.CompletableFuture<T>.WebAsyncTask: a Callable with a timeout and a custom executor.SseEmitter/ResponseBodyEmitter: stream events.Separately, @Async runs a service method in the background ("fire and forget", or a returned future).
@GetMapping("/reports/{id}")
CompletableFuture<ReportDto> report(@PathVariable long id) {
return reportService.generateAsync(id); // the request thread is released while the report builds
}
Short answer:
Key points to cover:
Learn it in depth → Horizontal vs Vertical Scaling
Q: What does MockMvc not test?
A: The real servlet container, and the network: real HTTP connectors, TLS, compression, and some filter orderings. Use @SpringBootTest(webEnvironment = RANDOM_PORT) with a real HTTP client for those.
Q: How do you serve a large file download efficiently?
A: Stream it, with ResponseEntity<Resource> (such as an InputStreamResource or FileSystemResource) or StreamingResponseBody, and set Content-Disposition and Content-Length. Or redirect to a pre-signed object-storage URL.
Q: What's the difference between @Async and async request processing?
A: @Async runs a method on another thread, and the caller carries on without waiting for the method to finish. Async request processing, by returning Callable or DeferredResult, keeps the HTTP request open, but releases the container thread until the result is ready.
Q: How do you share the HTTP session across instances?
A: Add spring-session-data-redis. Spring Session replaces the container's session with one stored in Redis, transparently, so HttpSession works the same on every instance.