or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdfunctional-programming.mdhttp-client.mdindex.mdrequest-processing.mdview-resolution.mdwebsocket.md

view-resolution.mddocs/

0

# View Resolution and Templating

1

2

Spring WebFlux provides a comprehensive view resolution system supporting multiple template engines including FreeMarker and script templates for server-side rendering. The system integrates seamlessly with reactive streams and supports both traditional template rendering and modern JavaScript-based templating.

3

4

## Capabilities

5

6

### Core View Interfaces

7

8

Basic interfaces for view rendering and resolution in reactive web applications.

9

10

```java { .api }

11

interface View {

12

/**

13

* Return the supported media types for this view.

14

* @return list of supported media types

15

*/

16

List<MediaType> getSupportedMediaTypes();

17

18

/**

19

* Render the view given the specified model.

20

* @param model the model containing attributes for rendering

21

* @param contentType the content type selected for rendering

22

* @param exchange the current server exchange

23

* @return completion signal

24

*/

25

Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange);

26

}

27

28

interface ViewResolver {

29

/**

30

* Resolve the given view name to a View instance.

31

* @param viewName the name of the view to resolve

32

* @param locale the locale for which to resolve the view

33

* @return the resolved view, or empty if not found

34

*/

35

Mono<View> resolveViewName(String viewName, Locale locale);

36

}

37

```

38

39

**Usage Examples:**

40

41

```java

42

// Custom view implementation

43

public class CustomJsonView implements View {

44

45

private final ObjectMapper objectMapper;

46

47

public CustomJsonView(ObjectMapper objectMapper) {

48

this.objectMapper = objectMapper;

49

}

50

51

@Override

52

public List<MediaType> getSupportedMediaTypes() {

53

return List.of(MediaType.APPLICATION_JSON);

54

}

55

56

@Override

57

public Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange) {

58

try {

59

String json = objectMapper.writeValueAsString(model);

60

ServerHttpResponse response = exchange.getResponse();

61

62

response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

63

DataBuffer buffer = response.bufferFactory().wrap(json.getBytes());

64

65

return response.writeWith(Mono.just(buffer));

66

} catch (Exception e) {

67

return Mono.error(e);

68

}

69

}

70

}

71

72

// Custom view resolver

73

@Component

74

public class CustomViewResolver implements ViewResolver {

75

76

private final Map<String, View> views = new HashMap<>();

77

78

public CustomViewResolver(ObjectMapper objectMapper) {

79

views.put("json", new CustomJsonView(objectMapper));

80

views.put("xml", new CustomXmlView());

81

}

82

83

@Override

84

public Mono<View> resolveViewName(String viewName, Locale locale) {

85

View view = views.get(viewName);

86

return view != null ? Mono.just(view) : Mono.empty();

87

}

88

}

89

```

90

91

### Rendering Interface

92

93

Public API for view rendering with model and status configuration.

94

95

```java { .api }

96

interface Rendering {

97

/**

98

* Return the view name to be resolved.

99

*/

100

String name();

101

102

/**

103

* Return the model attributes for rendering.

104

*/

105

Map<String, Object> modelAttributes();

106

107

/**

108

* Return the HTTP status code for the response.

109

*/

110

HttpStatusCode status();

111

112

/**

113

* Return the HTTP headers for the response.

114

*/

115

HttpHeaders headers();

116

117

/**

118

* Create a new rendering builder with the specified view name.

119

* @param name the view name

120

* @return the rendering builder

121

*/

122

static Builder with(String name);

123

}

124

```

125

126

**Rendering Builder Interface:**

127

128

```java { .api }

129

interface Builder {

130

/**

131

* Add a model attribute.

132

* @param name the attribute name

133

* @param value the attribute value

134

* @return this builder for chaining

135

*/

136

Builder modelAttribute(String name, Object value);

137

138

/**

139

* Add multiple model attributes.

140

* @param attributes the attributes to add

141

* @return this builder for chaining

142

*/

143

Builder modelAttributes(Object... attributes);

144

145

/**

146

* Set the HTTP status code.

147

* @param status the status code

148

* @return this builder for chaining

149

*/

150

Builder status(HttpStatus status);

151

152

/**

153

* Add an HTTP header.

154

* @param headerName the header name

155

* @param headerValues the header values

156

* @return this builder for chaining

157

*/

158

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

159

160

/**

161

* Configure HTTP headers.

162

* @param headersConsumer the headers consumer

163

* @return this builder for chaining

164

*/

165

Builder headers(Consumer<HttpHeaders> headersConsumer);

166

167

/**

168

* Build the rendering instance.

169

* @return the rendering instance

170

*/

171

Rendering build();

172

}

173

```

174

175

**Usage Examples:**

176

177

```java

178

@RestController

179

public class ViewController {

180

181

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

182

public Mono<Rendering> showUser(@PathVariable String id) {

183

return userService.findById(id)

184

.map(user -> Rendering.with("user-profile")

185

.modelAttribute("user", user)

186

.modelAttribute("title", "User Profile")

187

.status(HttpStatus.OK)

188

.header("Cache-Control", "max-age=3600")

189

.build())

190

.switchIfEmpty(Mono.just(

191

Rendering.with("error/404")

192

.modelAttribute("message", "User not found")

193

.status(HttpStatus.NOT_FOUND)

194

.build()));

195

}

196

197

@GetMapping("/dashboard")

198

public Mono<Rendering> dashboard(Authentication auth) {

199

return Mono.zip(

200

userService.getCurrentUser(auth),

201

statisticsService.getUserStats(auth.getName()),

202

recentActivityService.getRecentActivity(auth.getName())

203

).map(tuple -> Rendering.with("dashboard")

204

.modelAttribute("user", tuple.getT1())

205

.modelAttribute("stats", tuple.getT2())

206

.modelAttribute("activities", tuple.getT3())

207

.build());

208

}

209

}

210

```

211

212

### Request Context

213

214

Context holder for request-specific information accessible in views and templates.

215

216

```java { .api }

217

class RequestContext {

218

/**

219

* Return the current ServerWebExchange.

220

*/

221

ServerWebExchange getExchange();

222

223

/**

224

* Return the model for the current request.

225

*/

226

Map<String, Object> getModel();

227

228

/**

229

* Return the locale for the current request.

230

*/

231

Locale getLocale();

232

233

/**

234

* Return the path variables for the current request.

235

*/

236

Map<String, String> getPathVariables();

237

}

238

```

239

240

**Usage Examples:**

241

242

```java

243

// Access request context in custom view

244

public class TemplateView implements View {

245

246

@Override

247

public Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange) {

248

RequestContext context = new RequestContext(exchange, model);

249

250

// Use context in template processing

251

String locale = context.getLocale().toString();

252

String path = exchange.getRequest().getPath().value();

253

254

// Add context information to model

255

Map<String, Object> enhancedModel = new HashMap<>(model);

256

enhancedModel.put("requestPath", path);

257

enhancedModel.put("locale", locale);

258

enhancedModel.put("contextPath", exchange.getRequest().getPath().contextPath().value());

259

260

return renderTemplate(enhancedModel, exchange);

261

}

262

}

263

```

264

265

### FreeMarker Template Engine

266

267

Configuration and view classes for integrating FreeMarker templates with Spring WebFlux.

268

269

```java { .api }

270

class FreeMarkerConfigurer {

271

/**

272

* Set the FreeMarker Configuration to be used by this configurer.

273

* @param configuration the FreeMarker configuration

274

*/

275

void setConfiguration(Configuration configuration);

276

277

/**

278

* Set the location of the FreeMarker template files.

279

* @param templateLoaderPath the template loader path

280

*/

281

void setTemplateLoaderPath(String templateLoaderPath);

282

283

/**

284

* Set multiple locations for FreeMarker template files.

285

* @param templateLoaderPaths the template loader paths

286

*/

287

void setTemplateLoaderPaths(String... templateLoaderPaths);

288

289

/**

290

* Set whether to prefer file system access for template loading.

291

* @param preferFileSystemAccess true to prefer file system access

292

*/

293

void setPreferFileSystemAccess(boolean preferFileSystemAccess);

294

}

295

296

class FreeMarkerView implements View {

297

/**

298

* Set the FreeMarker Configuration to use.

299

* @param configuration the FreeMarker configuration

300

*/

301

void setConfiguration(Configuration configuration);

302

303

/**

304

* Set the encoding for template files.

305

* @param encoding the encoding to use

306

*/

307

void setEncoding(String encoding);

308

309

@Override

310

List<MediaType> getSupportedMediaTypes();

311

312

@Override

313

Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange);

314

}

315

316

class FreeMarkerViewResolver implements ViewResolver {

317

/**

318

* Set the suffix for template files.

319

* @param suffix the file suffix (e.g., ".ftl")

320

*/

321

void setSuffix(String suffix);

322

323

/**

324

* Set the prefix for template files.

325

* @param prefix the file prefix

326

*/

327

void setPrefix(String prefix);

328

329

@Override

330

Mono<View> resolveViewName(String viewName, Locale locale);

331

}

332

```

333

334

**Usage Examples:**

335

336

```java

337

@Configuration

338

public class FreeMarkerConfig {

339

340

@Bean

341

public FreeMarkerConfigurer freeMarkerConfigurer() {

342

FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();

343

configurer.setTemplateLoaderPaths("classpath:/templates/", "file:/var/templates/");

344

configurer.setPreferFileSystemAccess(false);

345

346

// Custom FreeMarker configuration

347

Configuration config = new Configuration(Configuration.VERSION_2_3_31);

348

config.setDefaultEncoding("UTF-8");

349

config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

350

config.setLogTemplateExceptions(false);

351

352

configurer.setConfiguration(config);

353

return configurer;

354

}

355

356

@Bean

357

public FreeMarkerViewResolver freeMarkerViewResolver() {

358

FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();

359

resolver.setPrefix("");

360

resolver.setSuffix(".ftl");

361

resolver.setContentType(MediaType.TEXT_HTML);

362

resolver.setExposeRequestAttributes(true);

363

resolver.setExposeSessionAttributes(true);

364

resolver.setExposeSpringMacroHelpers(true);

365

return resolver;

366

}

367

}

368

369

// Controller using FreeMarker views

370

@Controller

371

public class HomeController {

372

373

@GetMapping("/")

374

public Mono<String> home(Model model) {

375

return dataService.getHomePageData()

376

.doOnNext(data -> {

377

model.addAttribute("data", data);

378

model.addAttribute("timestamp", Instant.now());

379

})

380

.then(Mono.just("home")); // Resolves to home.ftl

381

}

382

}

383

```

384

385

### Script Template Engine

386

387

Support for JavaScript-based template engines like Handlebars, Mustache, and React server-side rendering.

388

389

```java { .api }

390

class ScriptTemplateConfigurer {

391

/**

392

* Set the ScriptEngine to use.

393

* @param engine the script engine

394

*/

395

void setEngine(ScriptEngine engine);

396

397

/**

398

* Set the engine name for ScriptEngine lookup.

399

* @param engineName the name of the script engine

400

*/

401

void setEngineName(String engineName);

402

403

/**

404

* Set the scripts to load on engine initialization.

405

* @param scriptNames the script file names

406

*/

407

void setScripts(String... scriptNames);

408

409

/**

410

* Set the render object name in the script context.

411

* @param renderObject the render object name

412

*/

413

void setRenderObject(String renderObject);

414

415

/**

416

* Set the render function name to call.

417

* @param renderFunction the render function name

418

*/

419

void setRenderFunction(String renderFunction);

420

421

/**

422

* Set the character encoding for template files.

423

* @param charset the character encoding

424

*/

425

void setCharset(Charset charset);

426

}

427

428

class ScriptTemplateView implements View {

429

/**

430

* Set the ScriptEngine to use.

431

* @param engine the script engine

432

*/

433

void setEngine(ScriptEngine engine);

434

435

/**

436

* Set the engine name for lookup.

437

* @param engineName the engine name

438

*/

439

void setEngineName(String engineName);

440

441

/**

442

* Set the script resources to load.

443

* @param scripts the script resources

444

*/

445

void setScripts(String... scripts);

446

447

/**

448

* Set the render object name.

449

* @param renderObject the render object name

450

*/

451

void setRenderObject(String renderObject);

452

453

/**

454

* Set the render function name.

455

* @param renderFunction the render function name

456

*/

457

void setRenderFunction(String renderFunction);

458

459

@Override

460

List<MediaType> getSupportedMediaTypes();

461

462

@Override

463

Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange);

464

}

465

466

class ScriptTemplateViewResolver implements ViewResolver {

467

/**

468

* Set the view name prefix.

469

* @param prefix the view name prefix

470

*/

471

void setPrefix(String prefix);

472

473

/**

474

* Set the view name suffix.

475

* @param suffix the view name suffix

476

*/

477

void setSuffix(String suffix);

478

479

@Override

480

Mono<View> resolveViewName(String viewName, Locale locale);

481

}

482

```

483

484

**Usage Examples:**

485

486

```java

487

@Configuration

488

public class ScriptTemplateConfig {

489

490

@Bean

491

public ScriptTemplateConfigurer mustacheConfigurer() {

492

ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();

493

configurer.setEngineName("nashorn");

494

configurer.setScripts("classpath:mustache.js");

495

configurer.setRenderObject("Mustache");

496

configurer.setRenderFunction("render");

497

return configurer;

498

}

499

500

@Bean

501

public ScriptTemplateViewResolver mustacheViewResolver() {

502

ScriptTemplateViewResolver resolver = new ScriptTemplateViewResolver();

503

resolver.setPrefix("classpath:/templates/");

504

resolver.setSuffix(".mustache");

505

return resolver;

506

}

507

}

508

509

// React SSR configuration

510

@Configuration

511

public class ReactSsrConfig {

512

513

@Bean

514

public ScriptTemplateConfigurer reactConfigurer() {

515

ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();

516

configurer.setEngineName("nashorn");

517

configurer.setScripts(

518

"classpath:polyfill.js",

519

"classpath:react.js",

520

"classpath:react-dom-server.js",

521

"classpath:app-bundle.js"

522

);

523

configurer.setRenderFunction("renderReactComponent");

524

configurer.setCharset(StandardCharsets.UTF_8);

525

return configurer;

526

}

527

}

528

529

// Controller using script templates

530

@Controller

531

public class PageController {

532

533

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

534

public Mono<String> productPage(@PathVariable String id, Model model) {

535

return productService.findById(id)

536

.doOnNext(product -> {

537

model.addAttribute("product", product);

538

model.addAttribute("pageTitle", "Product: " + product.getName());

539

})

540

.then(Mono.just("product-page")); // Resolves to product-page.mustache

541

}

542

}

543

```

544

545

### View Resolution Result Handler

546

547

Handler for processing view rendering results and writing template responses.

548

549

```java { .api }

550

class ViewResolutionResultHandler implements HandlerResultHandler {

551

/**

552

* Create a new ViewResolutionResultHandler.

553

* @param viewResolvers the view resolvers to use

554

* @param contentTypeResolver the content type resolver

555

*/

556

ViewResolutionResultHandler(List<ViewResolver> viewResolvers,

557

RequestedContentTypeResolver contentTypeResolver);

558

559

@Override

560

boolean supports(HandlerResult result);

561

562

@Override

563

Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result);

564

}

565

```

566

567

**Usage Examples:**

568

569

```java

570

@Configuration

571

public class ViewConfig {

572

573

@Bean

574

public ViewResolutionResultHandler viewResolutionResultHandler(

575

List<ViewResolver> viewResolvers,

576

RequestedContentTypeResolver contentTypeResolver) {

577

578

ViewResolutionResultHandler handler = new ViewResolutionResultHandler(

579

viewResolvers, contentTypeResolver);

580

581

// Configure default views for content negotiation

582

handler.setDefaultViews(List.of(

583

new MappingJackson2JsonView(),

584

new MappingJackson2XmlView()

585

));

586

587

return handler;

588

}

589

590

@Bean

591

@Order(1)

592

public ViewResolver primaryViewResolver() {

593

return new FreeMarkerViewResolver();

594

}

595

596

@Bean

597

@Order(2)

598

public ViewResolver fallbackViewResolver() {

599

return new ScriptTemplateViewResolver();

600

}

601

}

602

```

603

604

### Advanced View Features

605

606

Additional view resolution features and customization options.

607

608

```java { .api }

609

// URL-based view resolver registration

610

class UrlBasedViewResolverRegistration {

611

UrlBasedViewResolverRegistration prefix(String prefix);

612

UrlBasedViewResolverRegistration suffix(String suffix);

613

UrlBasedViewResolverRegistration viewClass(Class<?> viewClass);

614

UrlBasedViewResolverRegistration viewNames(String... viewNames);

615

UrlBasedViewResolverRegistration attributes(Map<String, ?> attributes);

616

}

617

618

// Content negotiating view resolver

619

class ContentNegotiatingViewResolver implements ViewResolver {

620

void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager);

621

void setViewResolvers(List<ViewResolver> viewResolvers);

622

void setDefaultViews(List<View> defaultViews);

623

624

@Override

625

Mono<View> resolveViewName(String viewName, Locale locale);

626

}

627

```

628

629

**Usage Examples:**

630

631

```java

632

// Advanced view resolver configuration

633

@Configuration

634

public class AdvancedViewConfig {

635

636

@Bean

637

public ContentNegotiatingViewResolver contentNegotiatingViewResolver(

638

ContentNegotiationManager contentNegotiationManager,

639

List<ViewResolver> viewResolvers) {

640

641

ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();

642

resolver.setContentNegotiationManager(contentNegotiationManager);

643

resolver.setViewResolvers(viewResolvers);

644

645

// Default views for different content types

646

resolver.setDefaultViews(List.of(

647

new MappingJackson2JsonView(), // application/json

648

new MappingJackson2XmlView(), // application/xml

649

new AtomFeedView(), // application/atom+xml

650

new RssChannelView() // application/rss+xml

651

));

652

653

return resolver;

654

}

655

656

@Bean

657

public ContentNegotiationManager contentNegotiationManager() {

658

ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();

659

factory.addMediaType("json", MediaType.APPLICATION_JSON);

660

factory.addMediaType("xml", MediaType.APPLICATION_XML);

661

factory.addMediaType("atom", MediaType.APPLICATION_ATOM_XML);

662

factory.addMediaType("rss", MediaType.APPLICATION_RSS_XML);

663

factory.setDefaultContentType(MediaType.TEXT_HTML);

664

factory.afterPropertiesSet();

665

return factory.getObject();

666

}

667

}

668

669

// Multi-format controller

670

@Controller

671

public class ApiController {

672

673

@GetMapping("/api/users")

674

public Mono<String> users(Model model) {

675

return userService.findAll()

676

.collectList()

677

.doOnNext(users -> model.addAttribute("users", users))

678

.then(Mono.just("users")); // Content negotiation will select format

679

}

680

}

681

```