Comprehensive application framework and inversion of control container for the Java platform providing dependency injection, AOP, data access, transaction management, and web framework capabilities
Spring Web MVC is the traditional servlet-based web framework built on the Model-View-Controller architecture. It provides flexible request handling, comprehensive data binding, validation, and view resolution for building web applications.
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.39</version>
</dependency>
<!-- Spring Web (base web utilities) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.39</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.7</version>
</dependency>// Controller and Request Mapping
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
// Request/Response Binding
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.CookieValue;
// Model and View
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
// HTTP Response
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpHeaders;
// Configuration
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.context.annotation.Configuration;
// Exception Handling
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ControllerAdvice;
// Validation
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;// Central dispatcher for HTTP request handlers/controllers
public class DispatcherServlet extends FrameworkServlet {
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
// Set the WebApplicationContext for this servlet
public void setApplicationContext(ApplicationContext applicationContext);
// Initialize strategy objects used by this servlet
protected void initStrategies(ApplicationContext context);
// Process the actual dispatching to the handler
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
// Interface to provide configuration for web application
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
ServletContext getServletContext();
}// Interface for objects that define mapping between requests and handler objects
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
// Chain including handler and interceptors
public class HandlerExecutionChain {
public HandlerExecutionChain(Object handler);
public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors);
public Object getHandler();
public HandlerInterceptor[] getInterceptors();
public void addInterceptor(HandlerInterceptor interceptor);
public void addInterceptor(int index, HandlerInterceptor interceptor);
public void addInterceptors(HandlerInterceptor... interceptors);
}
// Interface for handler interceptors
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
}
}// MVC framework extension point for using any handler interface
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
// Adapter for @RequestMapping annotated handler methods
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers);
public void setCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers);
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters);
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer);
}// Holder for both Model and View in web MVC framework
public class ModelAndView {
public ModelAndView();
public ModelAndView(String viewName);
public ModelAndView(View view);
public ModelAndView(String viewName, Map<String, ?> model);
public ModelAndView(View view, Map<String, ?> model);
public ModelAndView(String viewName, String modelName, Object modelObject);
public void setViewName(String viewName);
public String getViewName();
public void setView(View view);
public View getView();
public boolean hasView();
public boolean isReference();
public Map<String, Object> getModel();
public ModelAndView addObject(String attributeName, Object attributeValue);
public ModelAndView addObject(Object attributeValue);
public ModelAndView addAllObjects(Map<String, ?> modelMap);
public void clear();
public boolean isEmpty();
}
// Interface for MVC View for web interaction
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
default String getContentType() {
return null;
}
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
// Interface for objects that can resolve views by name
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}// Indicates that an annotated class is a "Controller"
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
// Combination of @Controller and @ResponseBody
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
// Maps HTTP requests to handler methods of MVC controllers
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}// Shortcut annotations for specific HTTP methods
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
@AliasFor(annotation = RequestMapping.class)
String name() default "";
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {
// Same attributes as GetMapping
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PUT)
public @interface PutMapping {
// Same attributes as GetMapping
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.DELETE)
public @interface DeleteMapping {
// Same attributes as GetMapping
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PATCH)
public @interface PatchMapping {
// Same attributes as GetMapping
}// Binds request parameters to method parameters
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
// Binds URI template variables to method parameters
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
// Binds HTTP request body to method parameter
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
// Binds method return value to web response body
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
// Binds request header to method parameter
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
// Binds cookie value to method parameter
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}// Enables Spring MVC configuration
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
// Interface for customizing Spring MVC configuration
public interface WebMvcConfigurer {
default void configurePathMatch(PathMatchConfigurer configurer) {
}
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
default void addFormatters(FormatterRegistry registry) {
}
default void addInterceptors(InterceptorRegistry registry) {
}
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
default void addCorsMappings(CorsRegistry registry) {
}
default void addViewControllers(ViewControllerRegistry registry) {
}
default void configureViewResolvers(ViewResolverRegistry registry) {
}
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}
default Validator getValidator() {
return null;
}
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}// Strategy interface for converting from and to HTTP requests and responses
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
// Abstract base class for HttpMessageConverter implementations
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
protected AbstractHttpMessageConverter();
protected AbstractHttpMessageConverter(MediaType supportedMediaType);
protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes);
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes);
public List<MediaType> getSupportedMediaTypes();
protected abstract boolean supports(Class<?> clazz);
protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
protected abstract void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
// JSON message converter using Jackson
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public MappingJackson2HttpMessageConverter();
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper);
public void setObjectMapper(ObjectMapper objectMapper);
public ObjectMapper getObjectMapper();
public void setPrettyPrint(boolean prettyPrint);
}// Extension of HttpEntity that adds HttpStatus
public class ResponseEntity<T> extends HttpEntity<T> {
public ResponseEntity(HttpStatus status);
public ResponseEntity(T body, HttpStatus status);
public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status);
public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status);
public HttpStatus getStatusCode();
public int getStatusCodeValue();
// Builder pattern
public static BodyBuilder status(HttpStatus status);
public static BodyBuilder status(int status);
public static BodyBuilder ok();
public static <T> ResponseEntity<T> ok(T body);
public static BodyBuilder created(URI location);
public static BodyBuilder accepted();
public static HeadersBuilder<?> noContent();
public static BodyBuilder badRequest();
public static HeadersBuilder<?> notFound();
public static BodyBuilder unprocessableEntity();
// Builder interfaces
public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
<T> ResponseEntity<T> body(T body);
}
public interface HeadersBuilder<B extends HeadersBuilder<B>> {
B header(String headerName, String... headerValues);
B headers(HttpHeaders headers);
B allow(HttpMethod... allowedMethods);
B eTag(String etag);
B lastModified(ZonedDateTime lastModified);
B location(URI location);
B cacheControl(CacheControl cacheControl);
B varyBy(String... requestHeaders);
<T> ResponseEntity<T> build();
}
}// Annotation for handling exceptions in specific handler classes/methods
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}
// Specialization of @Component for classes that declare @ExceptionHandler methods
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
// Combination of @ControllerAdvice and @ResponseBody
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
@AliasFor(annotation = ControllerAdvice.class)
String[] value() default {};
@AliasFor(annotation = ControllerAdvice.class)
String[] basePackages() default {};
@AliasFor(annotation = ControllerAdvice.class)
Class<?>[] basePackageClasses() default {};
@AliasFor(annotation = ControllerAdvice.class)
Class<?>[] assignableTypes() default {};
@AliasFor(annotation = ControllerAdvice.class)
Class<? extends Annotation>[] annotations() default {};
}
// Annotation for mapping specific exception and/or HTTP status
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {
@AliasFor("code")
HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;
@AliasFor("value")
HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;
String reason() default "";
}@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String sort) {
Pageable pageable = PageRequest.of(page, size);
if (sort != null) {
pageable = PageRequest.of(page, size, Sort.by(sort));
}
Page<User> users = userService.findAll(pageable);
HttpHeaders headers = new HttpHeaders();
headers.add("X-Total-Count", String.valueOf(users.getTotalElements()));
return ResponseEntity.ok()
.headers(headers)
.body(users.getContent());
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
User user = userService.createUser(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(user.getId())
.toUri();
return ResponseEntity.created(location).body(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
User user = userService.updateUser(id, request);
return ResponseEntity.ok(user);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/search")
public ResponseEntity<List<User>> searchUsers(
@RequestParam String query,
@RequestParam(required = false) String email,
@RequestParam(required = false) LocalDate createdAfter) {
UserSearchCriteria criteria = UserSearchCriteria.builder()
.query(query)
.email(email)
.createdAfter(createdAfter)
.build();
List<User> users = userService.searchUsers(criteria);
return ResponseEntity.ok(users);
}
}// Request DTOs with validation
public class CreateUserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "First name is required")
private String firstName;
@NotBlank(message = "Last name is required")
private String lastName;
@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "Phone number should be valid")
private String phoneNumber;
// Constructors, getters, setters
}
public class UpdateUserRequest {
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@Email(message = "Email should be valid")
private String email;
private String firstName;
private String lastName;
private String phoneNumber;
// Constructors, getters, setters
}
// Response DTOs
public class UserResponse {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
private String phoneNumber;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Constructors, getters, setters
}
// Error response
public class ErrorResponse {
private String message;
private String code;
private LocalDateTime timestamp;
private String path;
private Map<String, String> validationErrors;
// Constructors, getters, setters
}@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(
UserNotFoundException ex,
HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.message(ex.getMessage())
.code("USER_NOT_FOUND")
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationErrors(
MethodArgumentNotValidException ex,
HttpServletRequest request) {
Map<String, String> validationErrors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
validationErrors.put(error.getField(), error.getDefaultMessage()));
ErrorResponse error = ErrorResponse.builder()
.message("Validation failed")
.code("VALIDATION_ERROR")
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.validationErrors(validationErrors)
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ErrorResponse> handleDataIntegrityViolation(
DataIntegrityViolationException ex,
HttpServletRequest request) {
String message = "Data integrity violation";
String code = "DATA_INTEGRITY_ERROR";
// Check for specific constraint violations
if (ex.getMessage().contains("users_username_key")) {
message = "Username already exists";
code = "DUPLICATE_USERNAME";
} else if (ex.getMessage().contains("users_email_key")) {
message = "Email already exists";
code = "DUPLICATE_EMAIL";
}
ErrorResponse error = ErrorResponse.builder()
.message(message)
.code(code)
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
Exception ex,
HttpServletRequest request) {
logger.error("Unexpected error occurred", ex);
ErrorResponse error = ErrorResponse.builder()
.message("An unexpected error occurred")
.code("INTERNAL_ERROR")
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ErrorResponse> handleMethodNotSupported(
HttpRequestMethodNotSupportedException ex,
HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.message("HTTP method not supported: " + ex.getMethod())
.code("METHOD_NOT_SUPPORTED")
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(error);
}
}@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// JSON converter
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
jsonConverter.setObjectMapper(objectMapper);
converters.add(jsonConverter);
// String converter
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converters.add(stringConverter);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Request logging interceptor
registry.addInterceptor(new RequestLoggingInterceptor())
.addPathPatterns("/api/**");
// Authentication interceptor
registry.addInterceptor(new AuthenticationInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/api/public/**");
// Rate limiting interceptor
registry.addInterceptor(new RateLimitingInterceptor())
.addPathPatterns("/api/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://myapp.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorParameter(false)
.favorPathExtension(false)
.ignoreAcceptHeader(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(false);
configurer.setUseSuffixPatternMatch(false);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Static resources
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
// API documentation
registry.addResourceHandler("/docs/**")
.addResourceLocations("classpath:/docs/")
.setCacheControl(CacheControl.maxAge(Duration.ofHours(1)));
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(30000);
configurer.setTaskExecutor(asyncTaskExecutor());
configurer.registerCallableInterceptors(new CallableProcessingInterceptor() {
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
// Custom async processing logic
}
});
}
@Bean
public TaskExecutor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-mvc-");
executor.initialize();
return executor;
}
}@RestController
@RequestMapping("/api/files")
public class FileController {
private final FileStorageService fileStorageService;
public FileController(FileStorageService fileStorageService) {
this.fileStorageService = fileStorageService;
}
@PostMapping("/upload")
public ResponseEntity<FileUploadResponse> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam(required = false) String description) {
// Validate file
if (file.isEmpty()) {
throw new InvalidFileException("File is empty");
}
if (file.getSize() > 10 * 1024 * 1024) { // 10MB limit
throw new InvalidFileException("File size exceeds limit");
}
String contentType = file.getContentType();
if (!isValidContentType(contentType)) {
throw new InvalidFileException("Invalid file type: " + contentType);
}
// Store file
StoredFile storedFile = fileStorageService.store(file, description);
FileUploadResponse response = FileUploadResponse.builder()
.id(storedFile.getId())
.filename(storedFile.getFilename())
.originalFilename(file.getOriginalFilename())
.contentType(contentType)
.size(file.getSize())
.uploadedAt(LocalDateTime.now())
.build();
return ResponseEntity.ok(response);
}
@PostMapping("/upload-multiple")
public ResponseEntity<List<FileUploadResponse>> uploadMultipleFiles(
@RequestParam("files") MultipartFile[] files) {
List<FileUploadResponse> responses = Arrays.stream(files)
.map(file -> uploadFile(file, null).getBody())
.collect(Collectors.toList());
return ResponseEntity.ok(responses);
}
@GetMapping("/{fileId}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileId) {
StoredFile storedFile = fileStorageService.findById(fileId);
Resource resource = fileStorageService.loadAsResource(storedFile.getPath());
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(storedFile.getContentType()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + storedFile.getFilename() + "\"")
.body(resource);
}
@DeleteMapping("/{fileId}")
public ResponseEntity<Void> deleteFile(@PathVariable String fileId) {
fileStorageService.delete(fileId);
return ResponseEntity.noContent().build();
}
private boolean isValidContentType(String contentType) {
return contentType != null && (
contentType.startsWith("image/") ||
contentType.equals("application/pdf") ||
contentType.equals("text/plain") ||
contentType.equals("application/msword") ||
contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
);
}
}
// Multipart resolver configuration
@Configuration
public class MultipartConfig {
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(50 * 1024 * 1024); // 50MB
resolver.setMaxUploadSizePerFile(10 * 1024 * 1024); // 10MB per file
resolver.setMaxInMemorySize(1024 * 1024); // 1MB
resolver.setDefaultEncoding("UTF-8");
return resolver;
}
}@RestController
@RequestMapping("/api/async")
public class AsyncController {
private final EmailService emailService;
private final ReportService reportService;
public AsyncController(EmailService emailService, ReportService reportService) {
this.emailService = emailService;
this.reportService = reportService;
}
@PostMapping("/send-email")
public Callable<ResponseEntity<String>> sendEmailAsync(@RequestBody EmailRequest request) {
return () -> {
// This runs in a separate thread
emailService.sendEmail(request.getTo(), request.getSubject(), request.getBody());
return ResponseEntity.ok("Email sent successfully");
};
}
@GetMapping("/report/{id}")
public DeferredResult<ResponseEntity<ReportResponse>> generateReport(@PathVariable Long id) {
DeferredResult<ResponseEntity<ReportResponse>> deferredResult = new DeferredResult<>(30000L);
// Set timeout handler
deferredResult.onTimeout(() -> {
deferredResult.setResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body(new ReportResponse("Report generation timed out")));
});
// Set error handler
deferredResult.onError(throwable -> {
deferredResult.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ReportResponse("Error generating report: " + throwable.getMessage())));
});
// Generate report asynchronously
CompletableFuture.supplyAsync(() -> reportService.generateReport(id))
.whenComplete((report, throwable) -> {
if (throwable != null) {
deferredResult.setErrorResult(throwable);
} else {
deferredResult.setResult(ResponseEntity.ok(report));
}
});
return deferredResult;
}
@PostMapping("/process-batch")
public ResponseEntity<String> processBatchAsync(@RequestBody List<ProcessRequest> requests) {
// Start async processing and return immediately
CompletableFuture.runAsync(() -> {
requests.forEach(request -> {
try {
processRequest(request);
} catch (Exception e) {
logger.error("Error processing request: " + request.getId(), e);
}
});
});
return ResponseEntity.accepted().body("Batch processing started");
}
@GetMapping("/stream-data")
public StreamingResponseBody streamData() {
return outputStream -> {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream))) {
for (int i = 1; i <= 1000; i++) {
writer.println("Data chunk " + i);
writer.flush();
// Simulate processing time
Thread.sleep(10);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
}
}@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
logger.info("Incoming request: {} {} from {}",
request.getMethod(),
request.getRequestURI(),
getClientIpAddress(request));
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
// Log after handler execution
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
logger.info("Completed request: {} {} - Status: {} - Duration: {} ms",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
if (ex != null) {
logger.error("Request completed with exception", ex);
}
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
}
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
private final AuthenticationService authenticationService;
public AuthenticationInterceptor(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = extractToken(request);
if (token == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"error\": \"Authentication token required\"}");
return false;
}
try {
UserDetails user = authenticationService.validateToken(token);
request.setAttribute("currentUser", user);
return true;
} catch (InvalidTokenException e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"error\": \"Invalid authentication token\"}");
return false;
}
}
private String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
}Spring Web MVC provides a comprehensive framework for building web applications with clean separation of concerns, flexible request handling, and robust integration with the Spring ecosystem.
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring