eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

Routing is a common concept that appears in most web development frameworks including Spring MVC.

A route is a URL pattern that is mapped to a handler. The handler can be a physical file, such as a downloadable asset in the web application or a class that processes the request, such as a controller in an MVC application.

In this tutorial, we’ll explore the aspect of routing in developing web applications with the Play Framework.

2. Setup

First, we’ll need to create a Java Play application. The details on how to set up the Play Framework on a machine are available in our introductory article.

By the end of the setup, we should have a working Play application that we can access from a browser.

3. HTTP Routing

So how does Play knows which controller to consult whenever we send an HTTP request? The answer to this question lies in the app/conf/routes configuration file.

Play’s router translates HTTP requests into action calls. HTTP requests are considered to be events in MVC architecture and the router reacts to them by consulting the routes file for which controller and which action in that controller to execute.

Each of these events supplies a router with two parameters: a request path with its query string and the request’s HTTP method.

4. Basic Routing With Play

For the router to do its work, the conf/routes file must define mappings of HTTP methods and URI patterns to appropriate controller actions:

GET     /     controllers.HomeController.index
GET     /     assets/*file controllers.Assets.versioned(path="/public", file: Asset)

All routes files must also map the static resources in the play-routing/public folder available to the client on the /assets endpoint.
Notice the syntax of defining HTTP routes, and the HTTP method space URI pattern space controller action.

5. URI Patterns

In this section, we will expound a little on URI patterns.

5.1. Static URI Patterns

The first three URI patterns above are static. This means that mapping of the URLs to resources occurs without any further processing in the controller actions.

As long as a controller method is called, it returns a static resource whose content is determined before the request.

5.2. Dynamic URI Patterns

The last URI pattern above is dynamic. This means that the controller action servicing a request on these URIs needs some information from the request to determine the response. In the above case, it expects a file name.

The normal sequence of events is that the router receives an event, picks the path from the URL, decodes its segments, and passes them to the controller.

Path and query parameters are then injected into the controller action as parameters. We’ll demonstrate this with an example in the next sections.

6. Advanced Routing With Play

In this section, we’ll discuss advanced options in routing using Dynamic URI Patterns in detail.

6.1. Simple Path Parameters

Simple path parameters are unnamed parameters in a request URL that appear after the host and port and are parsed in order of appearance.

Inside play-routing/app/HomeController.java, let’s create a new action:

public Result greet(String name) {
    return ok("Hello " + name);
}

We want to be able to pick a path parameter from the request URL and map it to the variable name.

The router will get those values from a route configuration.

So, let’s open play-routing/conf/routes and create a mapping for this new action:

GET     /greet/:name     controllers.HomeController.greet(name: String)

Notice how we inform a router that name is a dynamic path segment with the colon syntax and then go ahead to pass it as a parameter to the greet action call.

Now, let’s load http://locahost:9000/greet/john in the browser, and we’ll be greeted by name:

Hello john

It so happens that if our action parameter is of string type, we may pass it during the action call without specifying the parameter type, though this is not the same for other types.

Let’s spice up our /greet endpoint with age information.

Back to HomeController‘s greet action, we’ll change it to:

public Result greet(String name, int age) {
    return ok("Hello " + name + ", you are " + age + " years old");
}

And the route to:

GET     /greet/:name/:age               controllers.HomeController.greet(name: String, age: Integer)

Notice also the Scala syntax for declaring a variable, age: Integer. In Java, we would use the Integer age syntax. The Play Framework is built in Scala. Consequently, there is a lot of scala syntax.

Let’s load http://localhost:9000/greet/john/26:

Hello john, you are 26 years old

6.2. Wildcards in Path Parameters

In our routes configuration file, the last mapping is:

GET     /assets/*file  controllers.Assets.versioned(path="/public", file: Asset)

We use a wildcard in the dynamic part of the path. What we are telling Play is that whatever value replaces *file in the actual request should be parsed as a whole and not decoded like in other cases of path parameters.

In this example, the controller is a built-in one, Assets, which allows the client to download files from the play-routing/public folder. When we load http://localhost:9000/assets/images/favicon.png, we should see the image of the Play favicon in the browser since it’s present in the /public/images folder.

Let’s create our own example action in HomeController.java:

public Result introduceMe(String data) {
    String[] clientData = data.split(",");
    return ok("Your name is " + clientData[0] + ", you are " + clientData[1] + " years old");
}

Notice that in this action, we receive one String parameter and apply our logic to decode it. In this case, the logic is to split a comma-delimited String into an array. Previously, we depended on a router to decode this data for us.

With wildcards, we are on our own. We’re hoping that the client gets our syntax correct while passing this data in. Ideally, we should validate the incoming String before using it.

Let’s create a route to this action:

GET   /*data   controllers.HomeController.introduceMe(data)

Now load the URL http://localhost:9000/john,26. This will print:

Your name is john, you are 26 years old

6.3. Regex in Path Parameters

Just like wildcards, we can use regular expressions for the dynamic part. Let’s add an action that receives a number and returns its square:

public Result squareMe(Long num) {
    return ok(num + " Squared is " + (num * num));
}

Now we’ll add its route:

GET   /square/$num<[0-9]+>   controllers.HomeController.squareMe(num:Long)

Let’s place this route below the introduceMe route to introduce a new concept. We can only handle routes where the regex part is a positive integer with this routing configuration.

Now if we have placed the route as instructed in the previous paragraph, and we load http://localhost:9000/square/2, we should be greeted with an ArrayIndexOutOfBoundsException:

play2

If we check the error logs in the server console, we will realize that the action call was actually performed on introduceMe action rather than squareMe action. As said earlier about wildcards, we are on our own and we did not validate incoming data.

Instead of a comma-delimited string, the introduceMe method was called with the string “square/2“. Consequently, after splitting it, we got an array of size one. Trying to reach index then threw the exception.

Naturally, we would expect the call to be routed to the squareMe method. Why was it routed to introduceMe? The reason is a Play feature we’ll cover next called Routing Priority.

7. Routing Priority

If there is a conflict between routes as there is between squareMe and introduceMe, then Play picks the first route in declaration order.

Why is there a conflict? Because of the wildcard context path /*data matches any request URL apart from the base path /. So every route whose URI pattern uses wildcards should appear last in order.

Now let’s change the declaration order of the routes such that the introduceMe route comes after squareMe and reload:

2 Squared is 4

To test the power of regular expressions in a route, try loading http://locahost:9000/square/-1, a router will fail to match the squareMe route. Instead, it will match introduceMe, and we’ll get the ArrayIndexOutOfBoundsException again.

This is because -1 does not match by the provided regular expression, neither does any alphabetic character.

8. Parameters

Up until this point, we’ve covered the syntax for declaring parameter types in the routes file.

In this section, we’ll look at more options available to us when dealing with parameters in routes.

8.1. Parameters With Fixed Values

Sometimes we’ll want to use a fixed value for a parameter. This is our way of telling Play to use the path parameter provided or if the request context is the path /, then use a certain fixed value.

Another way of looking at it is having two endpoints or context paths leading to the same controller action — with one endpoint requiring a parameter from the request URL and defaulting to the other in case the said parameter is absent.

To demonstrate this, let’s add a writer() action to the HomeController:

public Result writer() {
    return ok("Routing in Play by Baeldung");
}

Assuming we don’t always want our API to return a String:

Routing in Play by Baeldung

We want to control it by sending the name of an author of the article along with the request, defaulting to the fixed value Baeldung only if the request does not have the author parameter.

So let’s further change the writer action by adding a parameter:

public Result writer(String author) {
    return ok("REST API with Play by " + author);
}

Let’s also see how to add a fixed value parameter to the route:

GET     /writer           controllers.HomeController.writer(author = "Baeldung")
GET     /writer/:author   controllers.HomeController.writer(author: String)

Notice how we now have two separate routes all leading to the HomeController.index action instead of one.

When we now load http://localhost:9000/writer from the browser we get:

Routing in Play by Baeldung

And when we load http://localhost:9000/writer/john, we get:

Routing in Play by john

8.2. Parameters With Default Values

Apart from having fixed values, parameters can also have default values. Both provide fallback values to the controller action parameters in case the request does not provide the required values.

The difference between the two is that fixed values are used as a fallback for path parameters while default values are used as a fallback for query parameters.

Path parameters are of the form http://localhost:9000/param1/param2 and query parameters are of the form http://localhost:9000/?param1=value1&param2=value2.

The second difference is in the syntax of declaring the two in a route. Fixed value parameters use the assignment operator as in:

author = "Baeldung"

While default values use a different type of assignment:

author ?= "Baeldung"

We use the ?= operator which conditionally assigns Baeldung to author in case author is found to contain no value.

To have a complete demonstration, let’s create the HomeController.writer action. Let’s say, apart from the author’s name which is a path parameter, we also want to pass author id as a query parameter which should default to 1 if not passed in the request.

We’ll change writer action to:

public Result writer(String author, int id) {
    return ok("Routing in Play by: " + author + " ID: " + id);
}

and the writer routes to:

GET     /writer           controllers.HomeController.writer(author="Baeldung", id: Int ?= 1)
GET     /writer/:author   controllers.HomeController.writer(author: String, id: Int ?= 1)

Now loading http://localhost:9000/writer we see:

Routing in Play by: Baeldung ID: 1

Hitting http://localhost:9000/writer?id=10 gives us:

Routing in Play by: Baeldung ID: 10

What about http://localhost:9000/writer/john?

Routing in Play by: john ID: 1

And finally, http://localhost:9000/writer/john?id=5 returns:

Routing in Play by: john ID: 5

9. Conclusion

In this article, we explored the notion of Routing in Play applications. We also have an article on building a RESTful API with Play Framework where the routing concepts in this tutorial are applied in a practical example.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LS – NPI (cat=REST)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)