Play Framework
Reactive web framework supporting Java/Scala. Features high development productivity through hot reload and asynchronous programming.
GitHub Overview
playframework/playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
Topics
Star History
Framework
Play Framework
Overview
Play Framework is a modern framework for developing high-productivity web applications in Scala and Java.
Details
Play Framework is an open-source web application framework developed by Lightbend (formerly Typesafe). Released initially in 2011, while influenced by Ruby on Rails and Django, it is designed to leverage the benefits of high performance on the JVM and functional programming. It adopts reactive architecture to achieve high concurrency through non-blocking I/O. Based on the Akka actor system, asynchronous processing enables efficient handling of large volumes of requests. With a design focused on developer experience, the hot reload feature immediately reflects changes, enabling rapid development cycles. It comprehensively provides functionality necessary for modern web development, including type-safe routing, template engines, JSON support, and RESTful API construction support. Supporting both Scala and Java, it has high compatibility with existing Java ecosystems and is suitable for enterprise application development.
Pros and Cons
Pros
- Reactive Architecture: High concurrency performance and scalability
- Hot Reload: Changes during development are immediately reflected
- Type Safety: Compile-time error detection and IDE support
- Asynchronous Processing: High performance through non-blocking I/O
- Scala and Java Support: Choice based on developer skill set
- Rich Plugins: Extension features like authentication, database, cache
- RESTful Support: Easy construction of JSON APIs
- Enterprise Ready: Proven track record in large-scale systems
Cons
- Learning Curve: Need to understand Scala-specific concepts and functional programming
- Complex Configuration: Configuration management complexity in large projects
- Memory Consumption: Large initial memory usage due to JVM-based nature
- Debugging Difficulty: Debugging asynchronous processing and reactive streams
- Limited Community: Smaller developer community compared to Rails or Express
- JVM Startup Time: Initial startup takes time in development environment
Key Links
- Play Framework Official Site
- Play Framework Official Documentation
- Play Framework GitHub Repository
- Play Framework Community
- Lightbend Tech Hub
- Akka
Code Examples
Hello World
// app/controllers/HomeController.scala
package controllers
import javax.inject._
import play.api._
import play.api.mvc._
@Singleton
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def index() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
def hello(name: String) = Action {
Ok(s"Hello $name!")
}
}
<!-- app/views/index.scala.html -->
@()
@main("Welcome to Play") {
<h1>Welcome to Play!</h1>
<p>Hello World from Play Framework</p>
}
Routing
# conf/routes
# Routes
# This file defines all application routes (Higher priority routes first)
# Home page
GET / controllers.HomeController.index
# Hello endpoint
GET /hello/:name controllers.HomeController.hello(name: String)
# Users API
GET /api/users controllers.UserController.list
POST /api/users controllers.UserController.create
GET /api/users/:id controllers.UserController.show(id: Long)
PUT /api/users/:id controllers.UserController.update(id: Long)
DELETE /api/users/:id controllers.UserController.delete(id: Long)
# Static assets
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
JSON API Creation
// app/controllers/UserController.scala
package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import models.User
import services.UserService
import scala.concurrent.{ExecutionContext, Future}
@Singleton
class UserController @Inject()(
val controllerComponents: ControllerComponents,
userService: UserService
)(implicit ec: ExecutionContext) extends BaseController {
implicit val userFormat: Format[User] = Json.format[User]
def list = Action.async {
userService.findAll().map { users =>
Ok(Json.toJson(users))
}
}
def show(id: Long) = Action.async {
userService.findById(id).map {
case Some(user) => Ok(Json.toJson(user))
case None => NotFound(Json.obj("error" -> "User not found"))
}
}
def create = Action.async(parse.json) { request =>
request.body.validate[User] match {
case JsSuccess(user, _) =>
userService.create(user).map { createdUser =>
Created(Json.toJson(createdUser))
}
case JsError(errors) =>
Future.successful(BadRequest(Json.obj("errors" -> JsError.toJson(errors))))
}
}
}
Database Operations (Slick)
// app/models/User.scala
package models
import slick.jdbc.PostgresProfile.api._
import java.time.LocalDateTime
case class User(
id: Option[Long] = None,
name: String,
email: String,
createdAt: Option[LocalDateTime] = None
)
class UserTable(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def email = column[String]("email")
def createdAt = column[LocalDateTime]("created_at")
def * = (id.?, name, email, createdAt.?) <> ((User.apply _).tupled, User.unapply)
}
object Users {
val table = TableQuery[UserTable]
}
// app/services/UserService.scala
package services
import javax.inject._
import models.{User, Users}
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile
import scala.concurrent.{ExecutionContext, Future}
import java.time.LocalDateTime
@Singleton
class UserService @Inject()(
protected val dbConfigProvider: DatabaseConfigProvider
)(implicit ec: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
def findAll(): Future[Seq[User]] = {
db.run(Users.table.result)
}
def findById(id: Long): Future[Option[User]] = {
db.run(Users.table.filter(_.id === id).result.headOption)
}
def create(user: User): Future[User] = {
val userWithTimestamp = user.copy(createdAt = Some(LocalDateTime.now()))
val insertQuery = Users.table returning Users.table.map(_.id) into ((user, id) => user.copy(id = Some(id)))
db.run(insertQuery += userWithTimestamp)
}
def update(id: Long, user: User): Future[Option[User]] = {
val updateQuery = Users.table.filter(_.id === id).update(user.copy(id = Some(id)))
db.run(updateQuery).flatMap {
case 0 => Future.successful(None)
case _ => findById(id)
}
}
def delete(id: Long): Future[Boolean] = {
db.run(Users.table.filter(_.id === id).delete).map(_ > 0)
}
}
Asynchronous Processing and Futures
// app/controllers/AsyncController.scala
package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.ws._
import play.api.libs.json._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
@Singleton
class AsyncController @Inject()(
val controllerComponents: ControllerComponents,
ws: WSClient
)(implicit ec: ExecutionContext) extends BaseController {
def fetchData = Action.async {
// Call multiple APIs in parallel
val futureResult1 = ws.url("https://api.example1.com/data").get()
val futureResult2 = ws.url("https://api.example2.com/data").get()
for {
result1 <- futureResult1
result2 <- futureResult2
} yield {
val combinedData = Json.obj(
"api1" -> result1.json,
"api2" -> result2.json
)
Ok(combinedData)
}
}
def processWithTimeout = Action.async {
val futureResult = processLongRunningTask()
// Timeout setting
val timeoutFuture = play.api.libs.concurrent.Futures.timeout(10.seconds)
Future.firstCompletedOf(Seq(futureResult, timeoutFuture)).map {
case result: String => Ok(result)
case _ => RequestTimeout("Processing timeout")
}
}
private def processLongRunningTask(): Future[String] = {
Future {
Thread.sleep(5000) // Simulation of heavy processing
"Task completed"
}
}
}
Form Processing and Validation
// app/controllers/FormController.scala
package controllers
import javax.inject._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
import models.User
import scala.concurrent.{ExecutionContext, Future}
case class UserForm(name: String, email: String, age: Int)
@Singleton
class FormController @Inject()(
val controllerComponents: ControllerComponents
)(implicit ec: ExecutionContext) extends BaseController {
val userForm = Form(
mapping(
"name" -> nonEmptyText(minLength = 2, maxLength = 50),
"email" -> email,
"age" -> number(min = 0, max = 120)
)(UserForm.apply)(UserForm.unapply)
)
def showForm = Action { implicit request =>
Ok(views.html.userForm(userForm))
}
def submitForm = Action.async { implicit request =>
userForm.bindFromRequest().fold(
formWithErrors => {
Future.successful(BadRequest(views.html.userForm(formWithErrors)))
},
userData => {
// Process form data
processUserData(userData).map { success =>
if (success) {
Redirect(routes.FormController.showForm())
.flashing("success" -> "User created successfully")
} else {
BadRequest(views.html.userForm(userForm.withGlobalError("Failed to create user")))
}
}
}
)
}
private def processUserData(userData: UserForm): Future[Boolean] = {
// Database save processing
Future.successful(true)
}
}
Testing
// test/controllers/HomeControllerSpec.scala
package controllers
import org.scalatestplus.play._
import org.scalatestplus.play.guice._
import play.api.test._
import play.api.test.Helpers._
import play.api.libs.json._
class HomeControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
"HomeController GET" should {
"render the index page from the application" in {
val controller = inject[HomeController]
val home = controller.index().apply(FakeRequest(GET, "/"))
status(home) mustBe OK
contentType(home) mustBe Some("text/html")
contentAsString(home) must include("Welcome to Play")
}
"render the hello page" in {
val controller = inject[HomeController]
val hello = controller.hello("Bob").apply(FakeRequest(GET, "/hello/Bob"))
status(hello) mustBe OK
contentType(hello) mustBe Some("text/plain")
contentAsString(hello) mustBe "Hello Bob!"
}
}
"UserController" should {
"return JSON user list" in {
val request = FakeRequest(GET, "/api/users")
val result = route(app, request).get
status(result) mustBe OK
contentType(result) mustBe Some("application/json")
val json = contentAsJson(result)
json mustBe a[JsArray]
}
}
}