Play Framework

Reactive web framework supporting Java/Scala. Features high development productivity through hot reload and asynchronous programming.

ScalaJavaFrameworkWeb DevelopmentReactiveAsynchronous

GitHub Overview

playframework/playframework

The Community Maintained High Velocity Web Framework For Java and Scala.

Stars12,580
Watchers618
Forks4,086
Created:September 7, 2011
Language:Scala
License:Apache License 2.0

Topics

frameworkhacktoberfestjavajvmplayplayframeworkreactiverestfulscalaweb-frameworkwebapps

Star History

playframework/playframework Star History
Data as of: 8/13/2025, 01:43 AM

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

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]
    }
  }
}