or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aop.mdcore-container.mddata-access.mdindex.mdintegration.mdmessaging.mdreactive-web.mdtesting.mdweb-framework.md

web-framework.mddocs/

0

# Web Framework (Spring MVC)

1

2

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.

3

4

## Maven Dependencies

5

6

```xml

7

<!-- Spring Web MVC -->

8

<dependency>

9

<groupId>org.springframework</groupId>

10

<artifactId>spring-webmvc</artifactId>

11

<version>5.3.39</version>

12

</dependency>

13

14

<!-- Spring Web (base web utilities) -->

15

<dependency>

16

<groupId>org.springframework</groupId>

17

<artifactId>spring-web</artifactId>

18

<version>5.3.39</version>

19

</dependency>

20

21

<!-- Servlet API -->

22

<dependency>

23

<groupId>javax.servlet</groupId>

24

<artifactId>javax.servlet-api</artifactId>

25

<version>4.0.1</version>

26

<scope>provided</scope>

27

</dependency>

28

29

<!-- Jackson for JSON processing -->

30

<dependency>

31

<groupId>com.fasterxml.jackson.core</groupId>

32

<artifactId>jackson-databind</artifactId>

33

<version>2.12.7</version>

34

</dependency>

35

```

36

37

## Core Imports

38

39

```java { .api }

40

// Controller and Request Mapping

41

import org.springframework.web.bind.annotation.RestController;

42

import org.springframework.web.bind.annotation.Controller;

43

import org.springframework.web.bind.annotation.RequestMapping;

44

import org.springframework.web.bind.annotation.GetMapping;

45

import org.springframework.web.bind.annotation.PostMapping;

46

import org.springframework.web.bind.annotation.PutMapping;

47

import org.springframework.web.bind.annotation.DeleteMapping;

48

import org.springframework.web.bind.annotation.PatchMapping;

49

50

// Request/Response Binding

51

import org.springframework.web.bind.annotation.RequestParam;

52

import org.springframework.web.bind.annotation.PathVariable;

53

import org.springframework.web.bind.annotation.RequestBody;

54

import org.springframework.web.bind.annotation.ResponseBody;

55

import org.springframework.web.bind.annotation.RequestHeader;

56

import org.springframework.web.bind.annotation.CookieValue;

57

58

// Model and View

59

import org.springframework.ui.Model;

60

import org.springframework.web.servlet.ModelAndView;

61

import org.springframework.web.servlet.View;

62

63

// HTTP Response

64

import org.springframework.http.ResponseEntity;

65

import org.springframework.http.HttpStatus;

66

import org.springframework.http.HttpHeaders;

67

68

// Configuration

69

import org.springframework.web.servlet.config.annotation.EnableWebMvc;

70

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

71

import org.springframework.context.annotation.Configuration;

72

73

// Exception Handling

74

import org.springframework.web.bind.annotation.ExceptionHandler;

75

import org.springframework.web.bind.annotation.ControllerAdvice;

76

77

// Validation

78

import org.springframework.validation.BindingResult;

79

import org.springframework.validation.annotation.Validated;

80

import javax.validation.Valid;

81

```

82

83

## Core MVC Components

84

85

### DispatcherServlet

86

87

```java { .api }

88

// Central dispatcher for HTTP request handlers/controllers

89

public class DispatcherServlet extends FrameworkServlet {

90

91

public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";

92

public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";

93

public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";

94

public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

95

public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";

96

public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";

97

public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";

98

public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";

99

public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";

100

101

// Set the WebApplicationContext for this servlet

102

public void setApplicationContext(ApplicationContext applicationContext);

103

104

// Initialize strategy objects used by this servlet

105

protected void initStrategies(ApplicationContext context);

106

107

// Process the actual dispatching to the handler

108

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception;

109

}

110

111

// Interface to provide configuration for web application

112

public interface WebApplicationContext extends ApplicationContext {

113

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

114

String SCOPE_REQUEST = "request";

115

String SCOPE_SESSION = "session";

116

String SCOPE_APPLICATION = "application";

117

String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

118

String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

119

String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

120

121

ServletContext getServletContext();

122

}

123

```

124

125

### Handler Mapping

126

127

```java { .api }

128

// Interface for objects that define mapping between requests and handler objects

129

public interface HandlerMapping {

130

String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";

131

String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";

132

String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

133

String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

134

String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

135

String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

136

String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

137

String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

138

139

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

140

}

141

142

// Chain including handler and interceptors

143

public class HandlerExecutionChain {

144

145

public HandlerExecutionChain(Object handler);

146

public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors);

147

148

public Object getHandler();

149

public HandlerInterceptor[] getInterceptors();

150

public void addInterceptor(HandlerInterceptor interceptor);

151

public void addInterceptor(int index, HandlerInterceptor interceptor);

152

public void addInterceptors(HandlerInterceptor... interceptors);

153

}

154

155

// Interface for handler interceptors

156

public interface HandlerInterceptor {

157

158

default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

159

return true;

160

}

161

162

default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

163

ModelAndView modelAndView) throws Exception {

164

}

165

166

default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,

167

Exception ex) throws Exception {

168

}

169

}

170

```

171

172

### Handler Adapter

173

174

```java { .api }

175

// MVC framework extension point for using any handler interface

176

public interface HandlerAdapter {

177

178

boolean supports(Object handler);

179

180

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

181

182

long getLastModified(HttpServletRequest request, Object handler);

183

}

184

185

// Adapter for @RequestMapping annotated handler methods

186

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter

187

implements BeanFactoryAware, InitializingBean {

188

189

public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers);

190

public void setCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers);

191

public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters);

192

public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer);

193

}

194

```

195

196

### Model and View

197

198

```java { .api }

199

// Holder for both Model and View in web MVC framework

200

public class ModelAndView {

201

202

public ModelAndView();

203

public ModelAndView(String viewName);

204

public ModelAndView(View view);

205

public ModelAndView(String viewName, Map<String, ?> model);

206

public ModelAndView(View view, Map<String, ?> model);

207

public ModelAndView(String viewName, String modelName, Object modelObject);

208

209

public void setViewName(String viewName);

210

public String getViewName();

211

public void setView(View view);

212

public View getView();

213

public boolean hasView();

214

public boolean isReference();

215

public Map<String, Object> getModel();

216

public ModelAndView addObject(String attributeName, Object attributeValue);

217

public ModelAndView addObject(Object attributeValue);

218

public ModelAndView addAllObjects(Map<String, ?> modelMap);

219

public void clear();

220

public boolean isEmpty();

221

}

222

223

// Interface for MVC View for web interaction

224

public interface View {

225

String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

226

String PATH_VARIABLES = View.class.getName() + ".pathVariables";

227

String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

228

229

default String getContentType() {

230

return null;

231

}

232

233

void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

234

}

235

236

// Interface for objects that can resolve views by name

237

public interface ViewResolver {

238

View resolveViewName(String viewName, Locale locale) throws Exception;

239

}

240

```

241

242

## Annotations and Request Mapping

243

244

### Controller Annotations

245

246

```java { .api }

247

// Indicates that an annotated class is a "Controller"

248

@Target(ElementType.TYPE)

249

@Retention(RetentionPolicy.RUNTIME)

250

@Documented

251

@Component

252

public @interface Controller {

253

@AliasFor(annotation = Component.class)

254

String value() default "";

255

}

256

257

// Combination of @Controller and @ResponseBody

258

@Target(ElementType.TYPE)

259

@Retention(RetentionPolicy.RUNTIME)

260

@Documented

261

@Controller

262

@ResponseBody

263

public @interface RestController {

264

@AliasFor(annotation = Controller.class)

265

String value() default "";

266

}

267

268

// Maps HTTP requests to handler methods of MVC controllers

269

@Target({ElementType.TYPE, ElementType.METHOD})

270

@Retention(RetentionPolicy.RUNTIME)

271

@Documented

272

@Mapping

273

public @interface RequestMapping {

274

String name() default "";

275

276

@AliasFor("path")

277

String[] value() default {};

278

279

@AliasFor("value")

280

String[] path() default {};

281

282

RequestMethod[] method() default {};

283

284

String[] params() default {};

285

286

String[] headers() default {};

287

288

String[] consumes() default {};

289

290

String[] produces() default {};

291

}

292

```

293

294

### HTTP Method Mappings

295

296

```java { .api }

297

// Shortcut annotations for specific HTTP methods

298

299

@Target(ElementType.METHOD)

300

@Retention(RetentionPolicy.RUNTIME)

301

@Documented

302

@RequestMapping(method = RequestMethod.GET)

303

public @interface GetMapping {

304

@AliasFor(annotation = RequestMapping.class)

305

String name() default "";

306

307

@AliasFor(annotation = RequestMapping.class)

308

String[] value() default {};

309

310

@AliasFor(annotation = RequestMapping.class)

311

String[] path() default {};

312

313

@AliasFor(annotation = RequestMapping.class)

314

String[] params() default {};

315

316

@AliasFor(annotation = RequestMapping.class)

317

String[] headers() default {};

318

319

@AliasFor(annotation = RequestMapping.class)

320

String[] consumes() default {};

321

322

@AliasFor(annotation = RequestMapping.class)

323

String[] produces() default {};

324

}

325

326

@Target(ElementType.METHOD)

327

@Retention(RetentionPolicy.RUNTIME)

328

@Documented

329

@RequestMapping(method = RequestMethod.POST)

330

public @interface PostMapping {

331

// Same attributes as GetMapping

332

}

333

334

@Target(ElementType.METHOD)

335

@Retention(RetentionPolicy.RUNTIME)

336

@Documented

337

@RequestMapping(method = RequestMethod.PUT)

338

public @interface PutMapping {

339

// Same attributes as GetMapping

340

}

341

342

@Target(ElementType.METHOD)

343

@Retention(RetentionPolicy.RUNTIME)

344

@Documented

345

@RequestMapping(method = RequestMethod.DELETE)

346

public @interface DeleteMapping {

347

// Same attributes as GetMapping

348

}

349

350

@Target(ElementType.METHOD)

351

@Retention(RetentionPolicy.RUNTIME)

352

@Documented

353

@RequestMapping(method = RequestMethod.PATCH)

354

public @interface PatchMapping {

355

// Same attributes as GetMapping

356

}

357

```

358

359

### Request Parameter Binding

360

361

```java { .api }

362

// Binds request parameters to method parameters

363

@Target(ElementType.PARAMETER)

364

@Retention(RetentionPolicy.RUNTIME)

365

@Documented

366

public @interface RequestParam {

367

@AliasFor("name")

368

String value() default "";

369

370

@AliasFor("value")

371

String name() default "";

372

373

boolean required() default true;

374

375

String defaultValue() default ValueConstants.DEFAULT_NONE;

376

}

377

378

// Binds URI template variables to method parameters

379

@Target(ElementType.PARAMETER)

380

@Retention(RetentionPolicy.RUNTIME)

381

@Documented

382

public @interface PathVariable {

383

@AliasFor("name")

384

String value() default "";

385

386

@AliasFor("value")

387

String name() default "";

388

389

boolean required() default true;

390

}

391

392

// Binds HTTP request body to method parameter

393

@Target(ElementType.PARAMETER)

394

@Retention(RetentionPolicy.RUNTIME)

395

@Documented

396

public @interface RequestBody {

397

boolean required() default true;

398

}

399

400

// Binds method return value to web response body

401

@Target({ElementType.TYPE, ElementType.METHOD})

402

@Retention(RetentionPolicy.RUNTIME)

403

@Documented

404

public @interface ResponseBody {

405

}

406

407

// Binds request header to method parameter

408

@Target(ElementType.PARAMETER)

409

@Retention(RetentionPolicy.RUNTIME)

410

@Documented

411

public @interface RequestHeader {

412

@AliasFor("name")

413

String value() default "";

414

415

@AliasFor("value")

416

String name() default "";

417

418

boolean required() default true;

419

420

String defaultValue() default ValueConstants.DEFAULT_NONE;

421

}

422

423

// Binds cookie value to method parameter

424

@Target(ElementType.PARAMETER)

425

@Retention(RetentionPolicy.RUNTIME)

426

@Documented

427

public @interface CookieValue {

428

@AliasFor("name")

429

String value() default "";

430

431

@AliasFor("value")

432

String name() default "";

433

434

boolean required() default true;

435

436

String defaultValue() default ValueConstants.DEFAULT_NONE;

437

}

438

```

439

440

## Configuration

441

442

### Web MVC Configuration

443

444

```java { .api }

445

// Enables Spring MVC configuration

446

@Retention(RetentionPolicy.RUNTIME)

447

@Target(ElementType.TYPE)

448

@Documented

449

@Import(DelegatingWebMvcConfiguration.class)

450

public @interface EnableWebMvc {

451

}

452

453

// Interface for customizing Spring MVC configuration

454

public interface WebMvcConfigurer {

455

456

default void configurePathMatch(PathMatchConfigurer configurer) {

457

}

458

459

default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

460

}

461

462

default void configureAsyncSupport(AsyncSupportConfigurer configurer) {

463

}

464

465

default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {

466

}

467

468

default void addFormatters(FormatterRegistry registry) {

469

}

470

471

default void addInterceptors(InterceptorRegistry registry) {

472

}

473

474

default void addResourceHandlers(ResourceHandlerRegistry registry) {

475

}

476

477

default void addCorsMappings(CorsRegistry registry) {

478

}

479

480

default void addViewControllers(ViewControllerRegistry registry) {

481

}

482

483

default void configureViewResolvers(ViewResolverRegistry registry) {

484

}

485

486

default void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {

487

}

488

489

default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {

490

}

491

492

default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

493

}

494

495

default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

496

}

497

498

default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {

499

}

500

501

default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {

502

}

503

504

default Validator getValidator() {

505

return null;

506

}

507

508

default MessageCodesResolver getMessageCodesResolver() {

509

return null;

510

}

511

}

512

```

513

514

## HTTP Message Conversion

515

516

### Message Converters

517

518

```java { .api }

519

// Strategy interface for converting from and to HTTP requests and responses

520

public interface HttpMessageConverter<T> {

521

522

boolean canRead(Class<?> clazz, MediaType mediaType);

523

524

boolean canWrite(Class<?> clazz, MediaType mediaType);

525

526

List<MediaType> getSupportedMediaTypes();

527

528

T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

529

530

void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

531

}

532

533

// Abstract base class for HttpMessageConverter implementations

534

public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {

535

536

protected AbstractHttpMessageConverter();

537

protected AbstractHttpMessageConverter(MediaType supportedMediaType);

538

protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes);

539

540

public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes);

541

public List<MediaType> getSupportedMediaTypes();

542

543

protected abstract boolean supports(Class<?> clazz);

544

protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

545

protected abstract void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

546

}

547

548

// JSON message converter using Jackson

549

public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {

550

551

public MappingJackson2HttpMessageConverter();

552

public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper);

553

554

public void setObjectMapper(ObjectMapper objectMapper);

555

public ObjectMapper getObjectMapper();

556

public void setPrettyPrint(boolean prettyPrint);

557

}

558

```

559

560

## Response Handling

561

562

### ResponseEntity

563

564

```java { .api }

565

// Extension of HttpEntity that adds HttpStatus

566

public class ResponseEntity<T> extends HttpEntity<T> {

567

568

public ResponseEntity(HttpStatus status);

569

public ResponseEntity(T body, HttpStatus status);

570

public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status);

571

public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status);

572

573

public HttpStatus getStatusCode();

574

public int getStatusCodeValue();

575

576

// Builder pattern

577

public static BodyBuilder status(HttpStatus status);

578

public static BodyBuilder status(int status);

579

public static BodyBuilder ok();

580

public static <T> ResponseEntity<T> ok(T body);

581

public static BodyBuilder created(URI location);

582

public static BodyBuilder accepted();

583

public static HeadersBuilder<?> noContent();

584

public static BodyBuilder badRequest();

585

public static HeadersBuilder<?> notFound();

586

public static BodyBuilder unprocessableEntity();

587

588

// Builder interfaces

589

public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {

590

<T> ResponseEntity<T> body(T body);

591

}

592

593

public interface HeadersBuilder<B extends HeadersBuilder<B>> {

594

B header(String headerName, String... headerValues);

595

B headers(HttpHeaders headers);

596

B allow(HttpMethod... allowedMethods);

597

B eTag(String etag);

598

B lastModified(ZonedDateTime lastModified);

599

B location(URI location);

600

B cacheControl(CacheControl cacheControl);

601

B varyBy(String... requestHeaders);

602

<T> ResponseEntity<T> build();

603

}

604

}

605

```

606

607

## Exception Handling

608

609

### Exception Handler Annotations

610

611

```java { .api }

612

// Annotation for handling exceptions in specific handler classes/methods

613

@Target(ElementType.METHOD)

614

@Retention(RetentionPolicy.RUNTIME)

615

@Documented

616

public @interface ExceptionHandler {

617

Class<? extends Throwable>[] value() default {};

618

}

619

620

// Specialization of @Component for classes that declare @ExceptionHandler methods

621

@Target(ElementType.TYPE)

622

@Retention(RetentionPolicy.RUNTIME)

623

@Documented

624

@Component

625

public @interface ControllerAdvice {

626

@AliasFor("basePackages")

627

String[] value() default {};

628

629

String[] basePackages() default {};

630

631

Class<?>[] basePackageClasses() default {};

632

633

Class<?>[] assignableTypes() default {};

634

635

Class<? extends Annotation>[] annotations() default {};

636

}

637

638

// Combination of @ControllerAdvice and @ResponseBody

639

@Target(ElementType.TYPE)

640

@Retention(RetentionPolicy.RUNTIME)

641

@Documented

642

@ControllerAdvice

643

@ResponseBody

644

public @interface RestControllerAdvice {

645

@AliasFor(annotation = ControllerAdvice.class)

646

String[] value() default {};

647

648

@AliasFor(annotation = ControllerAdvice.class)

649

String[] basePackages() default {};

650

651

@AliasFor(annotation = ControllerAdvice.class)

652

Class<?>[] basePackageClasses() default {};

653

654

@AliasFor(annotation = ControllerAdvice.class)

655

Class<?>[] assignableTypes() default {};

656

657

@AliasFor(annotation = ControllerAdvice.class)

658

Class<? extends Annotation>[] annotations() default {};

659

}

660

661

// Annotation for mapping specific exception and/or HTTP status

662

@Target({ElementType.TYPE, ElementType.METHOD})

663

@Retention(RetentionPolicy.RUNTIME)

664

@Documented

665

public @interface ResponseStatus {

666

@AliasFor("code")

667

HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;

668

669

@AliasFor("value")

670

HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;

671

672

String reason() default "";

673

}

674

```

675

676

## Practical Usage Examples

677

678

### Basic REST Controller

679

680

```java { .api }

681

@RestController

682

@RequestMapping("/api/users")

683

public class UserController {

684

685

private final UserService userService;

686

687

public UserController(UserService userService) {

688

this.userService = userService;

689

}

690

691

@GetMapping

692

public ResponseEntity<List<User>> getAllUsers(

693

@RequestParam(defaultValue = "0") int page,

694

@RequestParam(defaultValue = "10") int size,

695

@RequestParam(required = false) String sort) {

696

697

Pageable pageable = PageRequest.of(page, size);

698

if (sort != null) {

699

pageable = PageRequest.of(page, size, Sort.by(sort));

700

}

701

702

Page<User> users = userService.findAll(pageable);

703

704

HttpHeaders headers = new HttpHeaders();

705

headers.add("X-Total-Count", String.valueOf(users.getTotalElements()));

706

707

return ResponseEntity.ok()

708

.headers(headers)

709

.body(users.getContent());

710

}

711

712

@GetMapping("/{id}")

713

public ResponseEntity<User> getUserById(@PathVariable Long id) {

714

User user = userService.findById(id);

715

return ResponseEntity.ok(user);

716

}

717

718

@PostMapping

719

public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {

720

User user = userService.createUser(request);

721

722

URI location = ServletUriComponentsBuilder

723

.fromCurrentRequest()

724

.path("/{id}")

725

.buildAndExpand(user.getId())

726

.toUri();

727

728

return ResponseEntity.created(location).body(user);

729

}

730

731

@PutMapping("/{id}")

732

public ResponseEntity<User> updateUser(

733

@PathVariable Long id,

734

@Valid @RequestBody UpdateUserRequest request) {

735

736

User user = userService.updateUser(id, request);

737

return ResponseEntity.ok(user);

738

}

739

740

@DeleteMapping("/{id}")

741

public ResponseEntity<Void> deleteUser(@PathVariable Long id) {

742

userService.deleteUser(id);

743

return ResponseEntity.noContent().build();

744

}

745

746

@GetMapping("/search")

747

public ResponseEntity<List<User>> searchUsers(

748

@RequestParam String query,

749

@RequestParam(required = false) String email,

750

@RequestParam(required = false) LocalDate createdAfter) {

751

752

UserSearchCriteria criteria = UserSearchCriteria.builder()

753

.query(query)

754

.email(email)

755

.createdAfter(createdAfter)

756

.build();

757

758

List<User> users = userService.searchUsers(criteria);

759

return ResponseEntity.ok(users);

760

}

761

}

762

```

763

764

### Request/Response DTOs and Validation

765

766

```java { .api }

767

// Request DTOs with validation

768

public class CreateUserRequest {

769

770

@NotBlank(message = "Username is required")

771

@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")

772

private String username;

773

774

@NotBlank(message = "Email is required")

775

@Email(message = "Email should be valid")

776

private String email;

777

778

@NotBlank(message = "First name is required")

779

private String firstName;

780

781

@NotBlank(message = "Last name is required")

782

private String lastName;

783

784

@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "Phone number should be valid")

785

private String phoneNumber;

786

787

// Constructors, getters, setters

788

}

789

790

public class UpdateUserRequest {

791

792

@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")

793

private String username;

794

795

@Email(message = "Email should be valid")

796

private String email;

797

798

private String firstName;

799

private String lastName;

800

private String phoneNumber;

801

802

// Constructors, getters, setters

803

}

804

805

// Response DTOs

806

public class UserResponse {

807

private Long id;

808

private String username;

809

private String email;

810

private String firstName;

811

private String lastName;

812

private String phoneNumber;

813

private LocalDateTime createdAt;

814

private LocalDateTime updatedAt;

815

816

// Constructors, getters, setters

817

}

818

819

// Error response

820

public class ErrorResponse {

821

private String message;

822

private String code;

823

private LocalDateTime timestamp;

824

private String path;

825

private Map<String, String> validationErrors;

826

827

// Constructors, getters, setters

828

}

829

```

830

831

### Exception Handling

832

833

```java { .api }

834

@RestControllerAdvice

835

public class GlobalExceptionHandler {

836

837

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

838

839

@ExceptionHandler(UserNotFoundException.class)

840

public ResponseEntity<ErrorResponse> handleUserNotFound(

841

UserNotFoundException ex,

842

HttpServletRequest request) {

843

844

ErrorResponse error = ErrorResponse.builder()

845

.message(ex.getMessage())

846

.code("USER_NOT_FOUND")

847

.timestamp(LocalDateTime.now())

848

.path(request.getRequestURI())

849

.build();

850

851

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);

852

}

853

854

@ExceptionHandler(MethodArgumentNotValidException.class)

855

public ResponseEntity<ErrorResponse> handleValidationErrors(

856

MethodArgumentNotValidException ex,

857

HttpServletRequest request) {

858

859

Map<String, String> validationErrors = new HashMap<>();

860

ex.getBindingResult().getFieldErrors().forEach(error ->

861

validationErrors.put(error.getField(), error.getDefaultMessage()));

862

863

ErrorResponse error = ErrorResponse.builder()

864

.message("Validation failed")

865

.code("VALIDATION_ERROR")

866

.timestamp(LocalDateTime.now())

867

.path(request.getRequestURI())

868

.validationErrors(validationErrors)

869

.build();

870

871

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);

872

}

873

874

@ExceptionHandler(DataIntegrityViolationException.class)

875

public ResponseEntity<ErrorResponse> handleDataIntegrityViolation(

876

DataIntegrityViolationException ex,

877

HttpServletRequest request) {

878

879

String message = "Data integrity violation";

880

String code = "DATA_INTEGRITY_ERROR";

881

882

// Check for specific constraint violations

883

if (ex.getMessage().contains("users_username_key")) {

884

message = "Username already exists";

885

code = "DUPLICATE_USERNAME";

886

} else if (ex.getMessage().contains("users_email_key")) {

887

message = "Email already exists";

888

code = "DUPLICATE_EMAIL";

889

}

890

891

ErrorResponse error = ErrorResponse.builder()

892

.message(message)

893

.code(code)

894

.timestamp(LocalDateTime.now())

895

.path(request.getRequestURI())

896

.build();

897

898

return ResponseEntity.status(HttpStatus.CONFLICT).body(error);

899

}

900

901

@ExceptionHandler(Exception.class)

902

public ResponseEntity<ErrorResponse> handleGenericException(

903

Exception ex,

904

HttpServletRequest request) {

905

906

logger.error("Unexpected error occurred", ex);

907

908

ErrorResponse error = ErrorResponse.builder()

909

.message("An unexpected error occurred")

910

.code("INTERNAL_ERROR")

911

.timestamp(LocalDateTime.now())

912

.path(request.getRequestURI())

913

.build();

914

915

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);

916

}

917

918

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)

919

public ResponseEntity<ErrorResponse> handleMethodNotSupported(

920

HttpRequestMethodNotSupportedException ex,

921

HttpServletRequest request) {

922

923

ErrorResponse error = ErrorResponse.builder()

924

.message("HTTP method not supported: " + ex.getMethod())

925

.code("METHOD_NOT_SUPPORTED")

926

.timestamp(LocalDateTime.now())

927

.path(request.getRequestURI())

928

.build();

929

930

return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(error);

931

}

932

}

933

```

934

935

### MVC Configuration

936

937

```java { .api }

938

@Configuration

939

@EnableWebMvc

940

public class WebMvcConfig implements WebMvcConfigurer {

941

942

@Override

943

public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

944

// JSON converter

945

MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();

946

ObjectMapper objectMapper = new ObjectMapper();

947

objectMapper.registerModule(new JavaTimeModule());

948

objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

949

objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);

950

jsonConverter.setObjectMapper(objectMapper);

951

converters.add(jsonConverter);

952

953

// String converter

954

StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);

955

converters.add(stringConverter);

956

}

957

958

@Override

959

public void addInterceptors(InterceptorRegistry registry) {

960

// Request logging interceptor

961

registry.addInterceptor(new RequestLoggingInterceptor())

962

.addPathPatterns("/api/**");

963

964

// Authentication interceptor

965

registry.addInterceptor(new AuthenticationInterceptor())

966

.addPathPatterns("/api/**")

967

.excludePathPatterns("/api/auth/**", "/api/public/**");

968

969

// Rate limiting interceptor

970

registry.addInterceptor(new RateLimitingInterceptor())

971

.addPathPatterns("/api/**");

972

}

973

974

@Override

975

public void addCorsMappings(CorsRegistry registry) {

976

registry.addMapping("/api/**")

977

.allowedOrigins("http://localhost:3000", "https://myapp.com")

978

.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")

979

.allowedHeaders("*")

980

.allowCredentials(true)

981

.maxAge(3600);

982

}

983

984

@Override

985

public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

986

configurer

987

.favorParameter(false)

988

.favorPathExtension(false)

989

.ignoreAcceptHeader(false)

990

.defaultContentType(MediaType.APPLICATION_JSON)

991

.mediaType("json", MediaType.APPLICATION_JSON)

992

.mediaType("xml", MediaType.APPLICATION_XML);

993

}

994

995

@Override

996

public void configurePathMatch(PathMatchConfigurer configurer) {

997

configurer.setUseTrailingSlashMatch(false);

998

configurer.setUseSuffixPatternMatch(false);

999

}

1000

1001

@Override

1002

public void addResourceHandlers(ResourceHandlerRegistry registry) {

1003

// Static resources

1004

registry.addResourceHandler("/static/**")

1005

.addResourceLocations("classpath:/static/")

1006

.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));

1007

1008

// API documentation

1009

registry.addResourceHandler("/docs/**")

1010

.addResourceLocations("classpath:/docs/")

1011

.setCacheControl(CacheControl.maxAge(Duration.ofHours(1)));

1012

}

1013

1014

@Override

1015

public void configureAsyncSupport(AsyncSupportConfigurer configurer) {

1016

configurer.setDefaultTimeout(30000);

1017

configurer.setTaskExecutor(asyncTaskExecutor());

1018

configurer.registerCallableInterceptors(new CallableProcessingInterceptor() {

1019

@Override

1020

public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {

1021

// Custom async processing logic

1022

}

1023

});

1024

}

1025

1026

@Bean

1027

public TaskExecutor asyncTaskExecutor() {

1028

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

1029

executor.setCorePoolSize(5);

1030

executor.setMaxPoolSize(20);

1031

executor.setQueueCapacity(100);

1032

executor.setThreadNamePrefix("async-mvc-");

1033

executor.initialize();

1034

return executor;

1035

}

1036

}

1037

```

1038

1039

### File Upload and Multipart Support

1040

1041

```java { .api }

1042

@RestController

1043

@RequestMapping("/api/files")

1044

public class FileController {

1045

1046

private final FileStorageService fileStorageService;

1047

1048

public FileController(FileStorageService fileStorageService) {

1049

this.fileStorageService = fileStorageService;

1050

}

1051

1052

@PostMapping("/upload")

1053

public ResponseEntity<FileUploadResponse> uploadFile(

1054

@RequestParam("file") MultipartFile file,

1055

@RequestParam(required = false) String description) {

1056

1057

// Validate file

1058

if (file.isEmpty()) {

1059

throw new InvalidFileException("File is empty");

1060

}

1061

1062

if (file.getSize() > 10 * 1024 * 1024) { // 10MB limit

1063

throw new InvalidFileException("File size exceeds limit");

1064

}

1065

1066

String contentType = file.getContentType();

1067

if (!isValidContentType(contentType)) {

1068

throw new InvalidFileException("Invalid file type: " + contentType);

1069

}

1070

1071

// Store file

1072

StoredFile storedFile = fileStorageService.store(file, description);

1073

1074

FileUploadResponse response = FileUploadResponse.builder()

1075

.id(storedFile.getId())

1076

.filename(storedFile.getFilename())

1077

.originalFilename(file.getOriginalFilename())

1078

.contentType(contentType)

1079

.size(file.getSize())

1080

.uploadedAt(LocalDateTime.now())

1081

.build();

1082

1083

return ResponseEntity.ok(response);

1084

}

1085

1086

@PostMapping("/upload-multiple")

1087

public ResponseEntity<List<FileUploadResponse>> uploadMultipleFiles(

1088

@RequestParam("files") MultipartFile[] files) {

1089

1090

List<FileUploadResponse> responses = Arrays.stream(files)

1091

.map(file -> uploadFile(file, null).getBody())

1092

.collect(Collectors.toList());

1093

1094

return ResponseEntity.ok(responses);

1095

}

1096

1097

@GetMapping("/{fileId}")

1098

public ResponseEntity<Resource> downloadFile(@PathVariable String fileId) {

1099

StoredFile storedFile = fileStorageService.findById(fileId);

1100

Resource resource = fileStorageService.loadAsResource(storedFile.getPath());

1101

1102

return ResponseEntity.ok()

1103

.contentType(MediaType.parseMediaType(storedFile.getContentType()))

1104

.header(HttpHeaders.CONTENT_DISPOSITION,

1105

"attachment; filename=\"" + storedFile.getFilename() + "\"")

1106

.body(resource);

1107

}

1108

1109

@DeleteMapping("/{fileId}")

1110

public ResponseEntity<Void> deleteFile(@PathVariable String fileId) {

1111

fileStorageService.delete(fileId);

1112

return ResponseEntity.noContent().build();

1113

}

1114

1115

private boolean isValidContentType(String contentType) {

1116

return contentType != null && (

1117

contentType.startsWith("image/") ||

1118

contentType.equals("application/pdf") ||

1119

contentType.equals("text/plain") ||

1120

contentType.equals("application/msword") ||

1121

contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")

1122

);

1123

}

1124

}

1125

1126

// Multipart resolver configuration

1127

@Configuration

1128

public class MultipartConfig {

1129

1130

@Bean

1131

public MultipartResolver multipartResolver() {

1132

CommonsMultipartResolver resolver = new CommonsMultipartResolver();

1133

resolver.setMaxUploadSize(50 * 1024 * 1024); // 50MB

1134

resolver.setMaxUploadSizePerFile(10 * 1024 * 1024); // 10MB per file

1135

resolver.setMaxInMemorySize(1024 * 1024); // 1MB

1136

resolver.setDefaultEncoding("UTF-8");

1137

return resolver;

1138

}

1139

}

1140

```

1141

1142

### Async Processing

1143

1144

```java { .api }

1145

@RestController

1146

@RequestMapping("/api/async")

1147

public class AsyncController {

1148

1149

private final EmailService emailService;

1150

private final ReportService reportService;

1151

1152

public AsyncController(EmailService emailService, ReportService reportService) {

1153

this.emailService = emailService;

1154

this.reportService = reportService;

1155

}

1156

1157

@PostMapping("/send-email")

1158

public Callable<ResponseEntity<String>> sendEmailAsync(@RequestBody EmailRequest request) {

1159

return () -> {

1160

// This runs in a separate thread

1161

emailService.sendEmail(request.getTo(), request.getSubject(), request.getBody());

1162

return ResponseEntity.ok("Email sent successfully");

1163

};

1164

}

1165

1166

@GetMapping("/report/{id}")

1167

public DeferredResult<ResponseEntity<ReportResponse>> generateReport(@PathVariable Long id) {

1168

DeferredResult<ResponseEntity<ReportResponse>> deferredResult = new DeferredResult<>(30000L);

1169

1170

// Set timeout handler

1171

deferredResult.onTimeout(() -> {

1172

deferredResult.setResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)

1173

.body(new ReportResponse("Report generation timed out")));

1174

});

1175

1176

// Set error handler

1177

deferredResult.onError(throwable -> {

1178

deferredResult.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

1179

.body(new ReportResponse("Error generating report: " + throwable.getMessage())));

1180

});

1181

1182

// Generate report asynchronously

1183

CompletableFuture.supplyAsync(() -> reportService.generateReport(id))

1184

.whenComplete((report, throwable) -> {

1185

if (throwable != null) {

1186

deferredResult.setErrorResult(throwable);

1187

} else {

1188

deferredResult.setResult(ResponseEntity.ok(report));

1189

}

1190

});

1191

1192

return deferredResult;

1193

}

1194

1195

@PostMapping("/process-batch")

1196

public ResponseEntity<String> processBatchAsync(@RequestBody List<ProcessRequest> requests) {

1197

// Start async processing and return immediately

1198

CompletableFuture.runAsync(() -> {

1199

requests.forEach(request -> {

1200

try {

1201

processRequest(request);

1202

} catch (Exception e) {

1203

logger.error("Error processing request: " + request.getId(), e);

1204

}

1205

});

1206

});

1207

1208

return ResponseEntity.accepted().body("Batch processing started");

1209

}

1210

1211

@GetMapping("/stream-data")

1212

public StreamingResponseBody streamData() {

1213

return outputStream -> {

1214

try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream))) {

1215

for (int i = 1; i <= 1000; i++) {

1216

writer.println("Data chunk " + i);

1217

writer.flush();

1218

1219

// Simulate processing time

1220

Thread.sleep(10);

1221

}

1222

} catch (InterruptedException e) {

1223

Thread.currentThread().interrupt();

1224

}

1225

};

1226

}

1227

}

1228

```

1229

1230

### Custom Interceptors

1231

1232

```java { .api }

1233

@Component

1234

public class RequestLoggingInterceptor implements HandlerInterceptor {

1235

1236

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

1237

1238

@Override

1239

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

1240

long startTime = System.currentTimeMillis();

1241

request.setAttribute("startTime", startTime);

1242

1243

logger.info("Incoming request: {} {} from {}",

1244

request.getMethod(),

1245

request.getRequestURI(),

1246

getClientIpAddress(request));

1247

1248

return true;

1249

}

1250

1251

@Override

1252

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

1253

ModelAndView modelAndView) {

1254

// Log after handler execution

1255

}

1256

1257

@Override

1258

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,

1259

Exception ex) {

1260

long startTime = (Long) request.getAttribute("startTime");

1261

long endTime = System.currentTimeMillis();

1262

long duration = endTime - startTime;

1263

1264

logger.info("Completed request: {} {} - Status: {} - Duration: {} ms",

1265

request.getMethod(),

1266

request.getRequestURI(),

1267

response.getStatus(),

1268

duration);

1269

1270

if (ex != null) {

1271

logger.error("Request completed with exception", ex);

1272

}

1273

}

1274

1275

private String getClientIpAddress(HttpServletRequest request) {

1276

String xForwardedFor = request.getHeader("X-Forwarded-For");

1277

if (xForwardedFor != null && !xForwardedFor.isEmpty()) {

1278

return xForwardedFor.split(",")[0].trim();

1279

}

1280

1281

String xRealIp = request.getHeader("X-Real-IP");

1282

if (xRealIp != null && !xRealIp.isEmpty()) {

1283

return xRealIp;

1284

}

1285

1286

return request.getRemoteAddr();

1287

}

1288

}

1289

1290

@Component

1291

public class AuthenticationInterceptor implements HandlerInterceptor {

1292

1293

private final AuthenticationService authenticationService;

1294

1295

public AuthenticationInterceptor(AuthenticationService authenticationService) {

1296

this.authenticationService = authenticationService;

1297

}

1298

1299

@Override

1300

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

1301

throws Exception {

1302

1303

String token = extractToken(request);

1304

1305

if (token == null) {

1306

response.setStatus(HttpStatus.UNAUTHORIZED.value());

1307

response.getWriter().write("{\"error\": \"Authentication token required\"}");

1308

return false;

1309

}

1310

1311

try {

1312

UserDetails user = authenticationService.validateToken(token);

1313

request.setAttribute("currentUser", user);

1314

return true;

1315

} catch (InvalidTokenException e) {

1316

response.setStatus(HttpStatus.UNAUTHORIZED.value());

1317

response.getWriter().write("{\"error\": \"Invalid authentication token\"}");

1318

return false;

1319

}

1320

}

1321

1322

private String extractToken(HttpServletRequest request) {

1323

String authHeader = request.getHeader("Authorization");

1324

if (authHeader != null && authHeader.startsWith("Bearer ")) {

1325

return authHeader.substring(7);

1326

}

1327

return null;

1328

}

1329

}

1330

```

1331

1332

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.