0
# Routing DSL
1
2
Play Framework's Routing DSL provides a fluent API for building type-safe routes programmatically with support for up to 3 parameters. The DSL enables dynamic route creation and HTTP method handling with automatic parameter binding and asynchronous request processing.
3
4
## Capabilities
5
6
### Core Routing DSL
7
8
Main routing DSL class for creating programmatic routes with fluent method chaining.
9
10
```java { .api }
11
/**
12
* DSL for building routers with type-safe parameter handling up to 3 parameters
13
*/
14
public class RoutingDsl {
15
/** Create GET route with path pattern */
16
public PathPatternMatcher GET(String pathPattern);
17
18
/** Create HEAD route with path pattern */
19
public PathPatternMatcher HEAD(String pathPattern);
20
21
/** Create POST route with path pattern */
22
public PathPatternMatcher POST(String pathPattern);
23
24
/** Create PUT route with path pattern */
25
public PathPatternMatcher PUT(String pathPattern);
26
27
/** Create DELETE route with path pattern */
28
public PathPatternMatcher DELETE(String pathPattern);
29
30
/** Create PATCH route with path pattern */
31
public PathPatternMatcher PATCH(String pathPattern);
32
33
/** Create OPTIONS route with path pattern */
34
public PathPatternMatcher OPTIONS(String pathPattern);
35
36
/** Create route with custom HTTP method */
37
public PathPatternMatcher match(String method, String pathPattern);
38
39
/** Build the configured router */
40
public play.api.routing.Router build();
41
}
42
```
43
44
**Usage Examples:**
45
46
```java
47
import play.routing.RoutingDsl;
48
import play.mvc.Result;
49
import static play.mvc.Results.*;
50
51
// Basic routing setup
52
public class MyRoutes {
53
54
public Router createRouter() {
55
RoutingDsl routingDsl = new RoutingDsl();
56
57
return routingDsl
58
.GET("/").routeTo(() -> ok("Welcome to my application"))
59
.GET("/health").routeTo(() -> ok("OK"))
60
.POST("/api/data").routeTo(() -> ok("Data received"))
61
.build();
62
}
63
}
64
65
// RESTful API routes
66
public class APIRoutes {
67
68
public Router createAPIRouter() {
69
RoutingDsl dsl = new RoutingDsl();
70
71
return dsl
72
// User routes
73
.GET("/api/users").routeTo(this::getAllUsers)
74
.GET("/api/users/:id").routeTo(this::getUser)
75
.POST("/api/users").routeTo(this::createUser)
76
.PUT("/api/users/:id").routeTo(this::updateUser)
77
.DELETE("/api/users/:id").routeTo(this::deleteUser)
78
79
// Product routes
80
.GET("/api/products").routeTo(this::getAllProducts)
81
.GET("/api/products/:id").routeTo(this::getProduct)
82
.POST("/api/products").routeTo(this::createProduct)
83
84
// Custom methods
85
.match("PATCH", "/api/users/:id/status").routeTo(this::updateUserStatus)
86
.build();
87
}
88
89
private Result getAllUsers() {
90
// Implementation
91
return ok("All users");
92
}
93
94
private Result getUser(String id) {
95
// Implementation with parameter
96
return ok("User: " + id);
97
}
98
99
// ... other methods
100
}
101
```
102
103
### Path Pattern Matcher
104
105
Route pattern matcher supporting different parameter arities with both synchronous and asynchronous action handling.
106
107
```java { .api }
108
/**
109
* Route pattern matcher for different parameter arities
110
*/
111
public class RoutingDsl.PathPatternMatcher {
112
/** Route with no parameters */
113
public RoutingDsl routeTo(F.Function0<Result> action);
114
115
/** Route with 1 parameter */
116
public <A1> RoutingDsl routeTo(F.Function<A1, Result> action);
117
118
/** Route with 2 parameters */
119
public <A1, A2> RoutingDsl routeTo(F.Function2<A1, A2, Result> action);
120
121
/** Route with 3 parameters */
122
public <A1, A2, A3> RoutingDsl routeTo(F.Function3<A1, A2, A3, Result> action);
123
124
/** Asynchronous route with no parameters */
125
public RoutingDsl routeAsync(F.Function0<F.Promise<Result>> action);
126
127
/** Asynchronous route with 1 parameter */
128
public <A1> RoutingDsl routeAsync(F.Function<A1, F.Promise<Result>> action);
129
130
/** Asynchronous route with 2 parameters */
131
public <A1, A2> RoutingDsl routeAsync(F.Function2<A1, A2, F.Promise<Result>> action);
132
133
/** Asynchronous route with 3 parameters */
134
public <A1, A2, A3> RoutingDsl routeAsync(F.Function3<A1, A2, A3, F.Promise<Result>> action);
135
}
136
```
137
138
**Usage Examples:**
139
140
```java
141
// Synchronous routes with parameters
142
public Router createSyncRoutes() {
143
RoutingDsl dsl = new RoutingDsl();
144
145
return dsl
146
// No parameters
147
.GET("/").routeTo(() -> ok("Home"))
148
149
// One parameter
150
.GET("/users/:id").routeTo((String id) ->
151
ok("User ID: " + id))
152
153
// Two parameters
154
.GET("/users/:userId/posts/:postId").routeTo((String userId, String postId) ->
155
ok("User: " + userId + ", Post: " + postId))
156
157
// Three parameters
158
.GET("/categories/:cat/products/:prod/reviews/:rev").routeTo(
159
(String cat, String prod, String rev) ->
160
ok("Category: " + cat + ", Product: " + prod + ", Review: " + rev))
161
162
.build();
163
}
164
165
// Asynchronous routes
166
public Router createAsyncRoutes() {
167
RoutingDsl dsl = new RoutingDsl();
168
169
return dsl
170
// Async no parameters
171
.GET("/async").routeAsync(() ->
172
F.Promise.promise(() -> ok("Async response")))
173
174
// Async with parameter
175
.GET("/async/users/:id").routeAsync((String id) ->
176
userService.findByIdAsync(id)
177
.map(user -> ok(Json.toJson(user))))
178
179
// Async with multiple parameters
180
.GET("/async/search/:type/:query").routeAsync((String type, String query) ->
181
searchService.searchAsync(type, query)
182
.map(results -> ok(Json.toJson(results))))
183
184
.build();
185
}
186
```
187
188
## Advanced Usage Patterns
189
190
### Type-Safe Parameter Handling
191
192
```java
193
// Custom parameter binding with type conversion
194
public class TypeSafeRoutes {
195
196
public Router createTypeSafeRouter() {
197
RoutingDsl dsl = new RoutingDsl();
198
199
return dsl
200
// String parameters (default)
201
.GET("/users/:id").routeTo(this::getUserString)
202
203
// Integer conversion
204
.GET("/products/:id").routeTo((String idStr) -> {
205
try {
206
Integer id = Integer.parseInt(idStr);
207
return getProduct(id);
208
} catch (NumberFormatException e) {
209
return badRequest("Invalid product ID");
210
}
211
})
212
213
// UUID conversion
214
.GET("/orders/:uuid").routeTo((String uuidStr) -> {
215
try {
216
UUID uuid = UUID.fromString(uuidStr);
217
return getOrder(uuid);
218
} catch (IllegalArgumentException e) {
219
return badRequest("Invalid UUID format");
220
}
221
})
222
223
.build();
224
}
225
226
private Result getUserString(String id) {
227
return ok("User: " + id);
228
}
229
230
private Result getProduct(Integer id) {
231
return ok("Product ID: " + id);
232
}
233
234
private Result getOrder(UUID uuid) {
235
return ok("Order UUID: " + uuid);
236
}
237
}
238
```
239
240
### REST API Builder
241
242
```java
243
// Comprehensive REST API builder
244
public class RESTAPIBuilder {
245
246
private final RoutingDsl dsl = new RoutingDsl();
247
248
public <T> RESTAPIBuilder resource(String basePath, RESTController<T> controller) {
249
dsl.GET(basePath).routeTo(controller::index)
250
.GET(basePath + "/:id").routeTo(controller::show)
251
.POST(basePath).routeTo(controller::create)
252
.PUT(basePath + "/:id").routeTo(controller::update)
253
.DELETE(basePath + "/:id").routeTo(controller::delete);
254
255
return this;
256
}
257
258
public <T> RESTAPIBuilder asyncResource(String basePath, AsyncRESTController<T> controller) {
259
dsl.GET(basePath).routeAsync(controller::indexAsync)
260
.GET(basePath + "/:id").routeAsync(controller::showAsync)
261
.POST(basePath).routeAsync(controller::createAsync)
262
.PUT(basePath + "/:id").routeAsync(controller::updateAsync)
263
.DELETE(basePath + "/:id").routeAsync(controller::deleteAsync);
264
265
return this;
266
}
267
268
public Router build() {
269
return dsl.build();
270
}
271
}
272
273
// REST controller interface
274
public interface RESTController<T> {
275
Result index();
276
Result show(String id);
277
Result create();
278
Result update(String id);
279
Result delete(String id);
280
}
281
282
// Async REST controller interface
283
public interface AsyncRESTController<T> {
284
F.Promise<Result> indexAsync();
285
F.Promise<Result> showAsync(String id);
286
F.Promise<Result> createAsync();
287
F.Promise<Result> updateAsync(String id);
288
F.Promise<Result> deleteAsync(String id);
289
}
290
291
// Usage
292
public Router createRESTAPI() {
293
return new RESTAPIBuilder()
294
.resource("/api/users", new UserController())
295
.resource("/api/products", new ProductController())
296
.asyncResource("/api/orders", new OrderController())
297
.build();
298
}
299
```
300
301
### Middleware and Filters
302
303
```java
304
// Route-specific middleware using action composition
305
public class MiddlewareRoutes {
306
307
public Router createProtectedRoutes() {
308
RoutingDsl dsl = new RoutingDsl();
309
310
return dsl
311
// Public routes
312
.GET("/").routeTo(() -> ok("Public home"))
313
.GET("/login").routeTo(() -> ok("Login page"))
314
315
// Protected routes with authentication
316
.GET("/dashboard").routeTo(() ->
317
withAuth(() -> ok("Dashboard")))
318
319
.GET("/profile/:id").routeTo((String id) ->
320
withAuth(() -> getUserProfile(id)))
321
322
// Admin routes with role check
323
.GET("/admin").routeTo(() ->
324
withRole("admin", () -> ok("Admin panel")))
325
326
.DELETE("/admin/users/:id").routeTo((String id) ->
327
withRole("admin", () -> deleteUser(id)))
328
329
.build();
330
}
331
332
private Result withAuth(Supplier<Result> action) {
333
if (isAuthenticated()) {
334
return action.get();
335
} else {
336
return unauthorized("Authentication required");
337
}
338
}
339
340
private Result withRole(String role, Supplier<Result> action) {
341
if (isAuthenticated() && hasRole(role)) {
342
return action.get();
343
} else {
344
return forbidden("Insufficient permissions");
345
}
346
}
347
348
private boolean isAuthenticated() {
349
// Check authentication
350
return true; // Placeholder
351
}
352
353
private boolean hasRole(String role) {
354
// Check user role
355
return true; // Placeholder
356
}
357
358
private Result getUserProfile(String id) {
359
return ok("Profile for user: " + id);
360
}
361
362
private Result deleteUser(String id) {
363
return ok("Deleted user: " + id);
364
}
365
}
366
```
367
368
### Dynamic Route Generation
369
370
```java
371
// Dynamic route generation based on configuration
372
public class DynamicRoutes {
373
374
public Router createDynamicRouter(Configuration config) {
375
RoutingDsl dsl = new RoutingDsl();
376
377
// Load route configurations
378
List<RouteConfig> routeConfigs = loadRouteConfigs(config);
379
380
for (RouteConfig routeConfig : routeConfigs) {
381
addRouteFromConfig(dsl, routeConfig);
382
}
383
384
return dsl.build();
385
}
386
387
private void addRouteFromConfig(RoutingDsl dsl, RouteConfig config) {
388
PathPatternMatcher matcher;
389
390
switch (config.method.toUpperCase()) {
391
case "GET":
392
matcher = dsl.GET(config.pattern);
393
break;
394
case "POST":
395
matcher = dsl.POST(config.pattern);
396
break;
397
case "PUT":
398
matcher = dsl.PUT(config.pattern);
399
break;
400
case "DELETE":
401
matcher = dsl.DELETE(config.pattern);
402
break;
403
default:
404
matcher = dsl.match(config.method, config.pattern);
405
}
406
407
// Add route handler based on parameter count
408
switch (config.parameterCount) {
409
case 0:
410
matcher.routeTo(() -> handleRoute(config));
411
break;
412
case 1:
413
matcher.routeTo((String p1) -> handleRoute(config, p1));
414
break;
415
case 2:
416
matcher.routeTo((String p1, String p2) -> handleRoute(config, p1, p2));
417
break;
418
case 3:
419
matcher.routeTo((String p1, String p2, String p3) -> handleRoute(config, p1, p2, p3));
420
break;
421
}
422
}
423
424
private List<RouteConfig> loadRouteConfigs(Configuration config) {
425
// Load from configuration
426
return new ArrayList<>(); // Placeholder
427
}
428
429
private Result handleRoute(RouteConfig config, String... params) {
430
// Dynamic route handling logic
431
return ok("Handled route: " + config.pattern + " with params: " + Arrays.toString(params));
432
}
433
434
private static class RouteConfig {
435
String method;
436
String pattern;
437
int parameterCount;
438
String handler;
439
}
440
}
441
```
442
443
### Error Handling and Validation
444
445
```java
446
// Route-level error handling and validation
447
public class ValidatedRoutes {
448
449
public Router createValidatedRouter() {
450
RoutingDsl dsl = new RoutingDsl();
451
452
return dsl
453
// Validated single parameter
454
.GET("/users/:id").routeTo((String id) ->
455
validateAndHandle(id, this::isValidUserId, this::getUser))
456
457
// Validated multiple parameters
458
.GET("/posts/:userId/:postId").routeTo((String userId, String postId) ->
459
validateAndHandle2(userId, postId,
460
this::isValidUserId, this::isValidPostId,
461
this::getPost))
462
463
// Async validation
464
.GET("/async/validate/:id").routeAsync((String id) ->
465
validateAsync(id)
466
.thenCompose(valid -> {
467
if (valid) {
468
return F.Promise.promise(() -> ok("Valid: " + id));
469
} else {
470
return F.Promise.promise(() -> badRequest("Invalid: " + id));
471
}
472
}))
473
474
.build();
475
}
476
477
private Result validateAndHandle(String param, Predicate<String> validator,
478
Function<String, Result> handler) {
479
if (validator.test(param)) {
480
try {
481
return handler.apply(param);
482
} catch (Exception e) {
483
return internalServerError("Error processing: " + e.getMessage());
484
}
485
} else {
486
return badRequest("Invalid parameter: " + param);
487
}
488
}
489
490
private Result validateAndHandle2(String param1, String param2,
491
Predicate<String> validator1, Predicate<String> validator2,
492
BiFunction<String, String, Result> handler) {
493
if (validator1.test(param1) && validator2.test(param2)) {
494
try {
495
return handler.apply(param1, param2);
496
} catch (Exception e) {
497
return internalServerError("Error processing: " + e.getMessage());
498
}
499
} else {
500
return badRequest("Invalid parameters");
501
}
502
}
503
504
private F.Promise<Boolean> validateAsync(String param) {
505
return F.Promise.promise(() -> {
506
// Async validation logic
507
Thread.sleep(100); // Simulate async operation
508
return param != null && param.matches("\\d+");
509
});
510
}
511
512
private boolean isValidUserId(String id) {
513
return id != null && id.matches("\\d+");
514
}
515
516
private boolean isValidPostId(String id) {
517
return id != null && id.matches("\\d+");
518
}
519
520
private Result getUser(String id) {
521
return ok("User: " + id);
522
}
523
524
private Result getPost(String userId, String postId) {
525
return ok("User: " + userId + ", Post: " + postId);
526
}
527
}
528
```