Spring MVC and SSM Integration Guide with Modern Best Practices

Spring MVC Fundamentals

To build a Spring MVC application, start by declaring the required dependencies in your pom.xml:

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.33</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
    </dependency>
</dependencies>
<packaging>war</packaging>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <path>/app</path>
                <port>8080</port>
                <uriEncoding>UTF-8</uriEncoding>
            </configuration>
        </plugin>
    </plugins>
</build>

Controller Layer

Define RESTful endpoints using annotations:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public Map<String, Object> createUser(@RequestBody User user) {
        System.out.println("Creating user: " + user.getName());
        Map<String, Object> result = new HashMap<>();
        result.put("status", "success");
        result.put("data", user);
        return result;
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> fetchUser(@PathVariable Integer id) {
        User mockUser = new User();
        mockUser.setId(id);
        mockUser.setName("Demo User");
        return ResponseEntity.ok(mockUser);
    }
}

Configuration Classes

Replace XML-based configuration with Java-based config:

@Configuration
@ComponentScan(basePackages = "com.example.controller")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/api/**");
    }
}

Servlet container initialization:

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

Parameter Binding and Data Handling

Primitive and POJO Parameters

Spring MVC automatically binds request parameters to method arguments:

@GetMapping("/search")
public String searchUsers(
        @RequestParam(value = "q", required = false) String query,
        @RequestParam(defaultValue = "0") int page,
        @ModelAttribute UserCriteria criteria) {
    
    // criteria is auto-populated from query string or form fields
    return "search-results";
}

For nested objects, use dot notation in URLs:

GET /search?name=John&address.city=New+York&address.zip=10001

JSON Payloads

Use @RequestBody for JSON deserialization (requires Jackson on classpath):

@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
        @PathVariable Long id,
        @RequestBody @Valid UserUpdateRequest updateRequest) {
    
    User updated = userService.update(id, updateRequest);
    return ResponseEntity.ok(updated);
}

Date Formatting

Customize date parsing using @DateTimeFormat:

@GetMapping("/events")
public List<Event> listEvents(
        @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam Date startDate,
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") @RequestParam Date endDate) {
    return eventService.findByRange(startDate, endDate);
}

RESTful Design Patterns

Adopt HTTP methods and resource-oriented URIs:

@RestController
@RequestMapping("/api/books")
public class BookController {

    @PostMapping
    public ResponseEntity<Book> create(@RequestBody Book book) { /* ... */ }

    @GetMapping("/{isbn}")
    public ResponseEntity<Book> read(@PathVariable String isbn) { /* ... */ }

    @PutMapping("/{isbn}")
    public ResponseEntity<Book> replace(@PathVariable String isbn, @RequestBody Book book) { /* ... */ }

    @PatchMapping("/{isbn}")
    public ResponseEntity<Book> update(@PathVariable String isbn, @RequestBody Map<String, Object> updates) { /* ... */ }

    @DeleteMapping("/{isbn}")
    public ResponseEntity<Void> delete(@PathVariable String isbn) { /* ... */ }
}

SSM Stack Integration

Core Dependencies

Include MyBatis, JDBC, and transaction management:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.21</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Data Access Layer

Use MyBatis annotations or XML mappers:

@Mapper
public interface BookMapper {

    @Select("SELECT * FROM books WHERE isbn = #{isbn}")
    Book selectByIsbn(@Param("isbn") String isbn);

    @Insert("INSERT INTO books (isbn, title, author) VALUES (#{isbn}, #{title}, #{author})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Book book);

    @Update("UPDATE books SET title = #{title}, author = #{author} WHERE isbn = #{isbn}")
    int update(Book book);

    @Delete("DELETE FROM books WHERE isbn = #{isbn}")
    int delete(@Param("isbn") String isbn);
}

Service Layer with Transactions

@Service
@Transactional
public class BookService {

    private final BookMapper bookMapper;

    public BookService(BookMapper bookMapper) {
        this.bookMapper = bookMapper;
    }

    public Book create(Book book) {
        bookMapper.insert(book);
        return book;
    }

    public Optional<Book> findByIsbn(String isbn) {
        return Optional.ofNullable(bookMapper.selectByIsbn(isbn));
    }
}

Unified Response Structure

Create a standardized response wrapper:

public record ApiResponse<T>(
        int code,
        String message,
        T data) {

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "OK", data);
    }

    public static <T> ApiResponse<T> error(int code, String msg) {
        return new ApiResponse<>(code, msg, null);
    }
}

Apply it in controllers:

@RestController
@RequestMapping("/api/books")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @PostMapping
    public ApiResponse<Book> create(@RequestBody Book book) {
        try {
            Book created = bookService.create(book);
            return ApiResponse.success(created);
        } catch (Exception e) {
            return ApiResponse.error(400, "Invalid input");
        }
    }
}

Global Exception Handling

Centralize error responses using @RestControllerAdvice:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ApiResponse<Void> handleNotFound(ResourceNotFoundException ex) {
        return ApiResponse.error(404, ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<Void> handleValidation(MethodArgumentNotValidException ex) {
        String errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(e -> e.getField() + ": " + e.getDefaultMessage())
                .collect(Collectors.joining("; "));
        return ApiResponse.error(400, "Validation failed: " + errors);
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleGeneric(Exception ex) {
        return ApiResponse.error(500, "Internal server error");
    }
}

Interceptor Implementation

Create reusable cross-cutting logic:

@Component
public class LoggingInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                            Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        logger.info("{} {} [{}]", request.getMethod(), request.getRequestURI(), startTime);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        long startTime = (Long) request.getAttribute("startTime");
        long duration = System.currentTimeMillis() - startTime;
        logger.info("Completed in {} ms with status {}", duration, response.getStatus());
    }
}

Spring Boot Simplification

Modern Spring Boot eliminates boilerplate configuraton:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Configure database and logging in application.yml:

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=UTC
    username: appuser
    password: secret
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: true
    properties:
      hibernate:
        format_sql: true

logging:
  level:
    root: INFO
    com.example: DEBUG

Enable MyBatis auto-configuration:

@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application { /* ... */ }

Tags: spring-mvc MyBatis spring-boot restful-api java-web

Posted on Tue, 01 Sep 2026 16:56:01 +0000 by mtb211