Play with Play: An Introduction to Play Framework

Play Framework

What is Play framework?

Play is a high-productivity Java and Scala web application framework that integrates components and APIs for modern web application development. Play includes all the components you need to build Web Applications and REST services, such as an integrated HTTP server, form handling, Cross-Site Request Forgery (CSRF) protection, a powerful routing mechanism, I18n support, and more.

Play saves precious development time by directly supporting everyday tasks and hot reloading so that you can immediately view the results of your work.

Play’s lightweight, stateless, web-friendly architecture uses Akka and Akka Streams under the covers to provide predictable and minimal resource consumption (CPU, memory, threads). Thanks to its reactive model, applications scale naturally-both horizontally and vertically.

Comparison with Spring.

How is Play different from Spring?

Which use case suits Play more than Spring?

These are the two most common question asked by a developer diving into the Play framework. Having worked on both frameworks, I have personally found Play to perform better than others.

The reasons:

  1. Developer productivity
    Spring takes a long time start and to see a change, you have to restart it (which can take minutes for a large app); the APIs are clunky; it has few built-in bells and whistles that you need to build a modern app. In fact, talk to developers at just about any company that has a reasonably large Spring MVC deployment and you’ll find a dirty secret: they all build custom frameworks on top of it (most of which are poorly tested, documented, supported, etc). Even the folks at Springsource itself had to build another layer on top of Spring MVC called Spring Boot.

    Because Spring MVC, itself, is damn-near unusable. On the other hand, Play can hot-reload changes, so you get in a wonderful code-refresh-code-refresh cycle; the APIs are powerful & composable; and it’s a modern framework with built-in support for most common tasks (JSON, web sockets, DB access, web service calls, etc).
  2. Non-blocking I/O
    Spring is blocking, which means any time you do I/O (make a remote service call, process a client request, etc), it holds up a thread. This makes it tough to scale, which is problematic, as most modern apps spend most of their time doing I/O. Play is fully non-blocking, which means it performs better in a service-oriented architecture (microservices) and it can support web sockets, streaming, etc. This opens up some amazing possibilities, such as BigPipe style streaming.
  3. Functional programming
    Spring is built around a bunch of mutable objects, annotations, XML configuration files, and other magic.
    This seems OK at first until something goes wrong, or you need to do something slightly custom, or you have a large app and can’t follow all the places that side effects are happening

    In short, most Spring MVC apps are a giant ball of Spaghetti.

    On the other hand, Play is built around a functional programming core. Just about everything is a function that returns a value, and they make excellent use of the type system (not only type-safe classes, but also type-safe routes, templates, build system, and more) which makes it much easier to reason about the code, compose and reuse pieces of your app, debug problems, plug-in custom behaviour, and so on.
  4. Better error reporting
    With Spring, you get a gigantic, incomprehensible stack trace in an obscure log file. With Play, you get a clear error message and a snippet of the broken code right in the browser. It’s also worth mentioning that Play makes much better use of the type system, so most errors you get are compile-time errors instead of run-time errors.

Below is a video explaining the reason why LinkedIn developers moved to Play.

How does play ensure non-blocking?

Consider the code:

Under the hood, Play uses a thread pool-sized to one thread per CPU core. One of these scarce threads, T1, executes the proxy action, running through the code from top to bottom, except the contents of the function passed to the map method, since that depends on a non-blocking I/O call that has not yet completed. Once T1 returns the AsyncResult, it moves on to process other requests. Later on, when the response from is finally available, another thread T2 (which may or may not be the same as T1) executes the function passed to the map method. At no point were either of the threads blocked waiting on the response from

A talk that shows the power of non-blocking I/O and functional programming by implementing BigPipe-style streaming in Play.

Let’s dive into the framework. How to create a new application?

The easiest way to get started with Play is to download the starter project.

Go to: Play Starter Projects.
Download one of the sample projects.
Unzip it and run “./sbt run” command in your terminal.
Go to localhost:9000 on your browser.
Your new Play application should be running.
To get to the local documentation for Play, navigate to localhost:9000/@documentation.

sbt new playframework/play-java-seed.g8

After that, you can either use a basic code editor or import your project in IntelliJ or Eclipse by using ScalaIDE.

Your project structure would look something like this.

Play has an idiomatic Java API and a separate idiomatic Scala API because the idioms differ between the two languages.

The packages in Play for Scala begin with play.api while the Java API lives under the Play package prefix.


The conf/routes file is the main route file. When you make a request to http://localhost:9000 your browser makes a GET request with a / path.

If you want to segregate your route files based on your own business logic, new routing files can be added. Let us assume we want to aggregate all the routing request to a Profile page to /profile route and all the requests on the Contact page to /contact route.

-> /profile profile.Routes

-> /contact contact.Routes

This can be done by defining custom routes on the conf/routes file.

After declaring the custom routes, all we need to do is create profile.routes and contact.routes and put respective routing on the files.

Any request with /profile on the URL would be handled by profile.routes and any request with /contact on the URL would be handled by contact.routes.

One of the reasons that Play compiles the routes file is to provide a reverse routing API so that you never have to hard code URLs into your application. Instead, you call a method in the reverse router, which returns the route defined by the “routes” file. This enables you to easily refactor your URLs without breaking your app.


Controllers in Play are responsible for handling a request and returning a response.

Extending the Controller class is optional. Doing so helps us inherit some inbuilt functions. The controller instance is created by using Guice, a dependency injection framework. The get() method returns a play.mvc.Result, which represents the HTTP response. ok() is a helper method defined by Play to return the response. In this case, the response is a status code “200 OK”. There are many other helpers like notFound and badRequest that wrap the general purpose play.mvc.Status API.

Below are the methods that can be inherited from Controller class.

Below are the various helper methods available:

Controllers in Play are internally asynchronous and non-blocking. Interceptors can be added to controllers in order to add security, logging, caching, and other custom behaviours. This is called Action Composition. In Play’s Java API, annotations are used to add the interceptors.


The test directory contains the unit, functional, and integration tests for your project. You can write your tests with any framework that has a JUnit compatible test runner.

Play has some specific helpers for JUnit: Specs2 (a Scala testing framework) and ScalaTest (another Scala testing framework).

Sample JUnit Test of a controller.


The conf/application.conf file contains your application’s default configuration. There, you can override config or define your own.

To read that config in Java, you need to inject a Config object:


Play uses the sbt build tool for managing dependencies, compiling the app, running the app, running the tests, and packaging the app. The project/ file specifies the version of sbt to use. Any sbt plugins can be added in the project/plugins.sbt file. The main definition of the build is defined in a file named build.sbt

The libraryDependencies section of the build.sbt defines the application dependencies that should be available in a public Maven repository. You can also add your own Maven repository using the resolvers setting. The dependencies in libraryDependencies are a comma-separated list in this form:

Play offers a lot of optional dependencies which can be included in the build via aliases.

Apart from this, Play’s build also supports sub-projects so that you can partition your application into multiple smaller pieces. This can improve build times and make different pieces more easily reusable.


Play looks complicated with its asynchronous nature on the first look but as you keep digging deeper, it turns out to be a very easy to go framework. This blog gave you a basic introduction to the framework. If you find it interesting and want to dig further, I am attaching a list of reference material that you can use.

1. Free Training Course by Lightbend.

2. Rest API with Play.

3. Official documentation.

4. List of play modules.

Originally published at on May 16, 2020.