CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework--spring

Comprehensive application framework and inversion of control container for the Java platform providing dependency injection, AOP, data access, transaction management, and web framework capabilities

Overview
Eval results
Files

web-framework.mddocs/

Web Framework (Spring MVC)

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.

Maven Dependencies

<!-- 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>

Core Imports

// 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;

Core MVC Components

DispatcherServlet

// 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();
}

Handler Mapping

// 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 {
    }
}

Handler Adapter

// 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);
}

Model and View

// 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;
}

Annotations and Request Mapping

Controller Annotations

// 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 {};
}

HTTP Method Mappings

// 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
}

Request Parameter Binding

// 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;
}

Configuration

Web MVC Configuration

// 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;
    }
}

HTTP Message Conversion

Message Converters

// 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);
}

Response Handling

ResponseEntity

// 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();
    }
}

Exception Handling

Exception Handler Annotations

// 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 "";
}

Practical Usage Examples

Basic REST Controller

@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/Response DTOs and Validation

// 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
}

Exception Handling

@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);
    }
}

MVC Configuration

@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;
    }
}

File Upload and Multipart Support

@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;
    }
}

Async Processing

@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();
            }
        };
    }
}

Custom Interceptors

@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

docs

aop.md

core-container.md

data-access.md

index.md

integration.md

messaging.md

reactive-web.md

testing.md

web-framework.md

tile.json