Docs / Programming & Development / Scala Play Framework

Scala Play Framework

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 358 views · 3 min read

Play Framework is a reactive web framework for Scala and Java that is built on Akka and Akka Streams. It provides a stateless, non-blocking architecture ideal for building scalable web applications and APIs. This guide covers building and deploying a Play Scala application on a VPS.

Project Setup

# Install sbt
curl -fL https://github.com/sbt/sbt/releases/download/v1.10.0/sbt-1.10.0.tgz | tar xz
sudo mv sbt /opt/
export PATH=$PATH:/opt/sbt/bin

# Create new Play project
sbt new playframework/play-scala-seed.g8
cd play-scala-seed

Application Structure

app/
├── controllers/
│   └── HomeController.scala
├── models/
│   └── User.scala
├── services/
│   └── UserService.scala
├── views/
│   └── index.scala.html
conf/
├── application.conf
├── routes
└── logback.xml
build.sbt
project/
└── plugins.sbt

Controller Example

// app/controllers/UserController.scala
package controllers

import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.{ExecutionContext, Future}

case class User(id: Long, name: String, email: String)

object User {
  implicit val format: OFormat[User] = Json.format[User]
}

case class CreateUser(name: String, email: String)

object CreateUser {
  implicit val reads: Reads[CreateUser] = Json.reads[CreateUser]
}

@Singleton
class UserController @Inject()(
    cc: ControllerComponents
)(implicit ec: ExecutionContext) extends AbstractController(cc) {

  def list(): Action[AnyContent] = Action.async {
    val users = List(
      User(1, "Alice", "alice@example.com"),
      User(2, "Bob", "bob@example.com")
    )
    Future.successful(Ok(Json.toJson(users)))
  }

  def get(id: Long): Action[AnyContent] = Action.async {
    Future.successful(Ok(Json.toJson(User(id, "Alice", "alice@example.com"))))
  }

  def create(): Action[JsValue] = Action.async(parse.json) { request =>
    request.body.validate[CreateUser].fold(
      errors => Future.successful(BadRequest(Json.obj("error" -> JsError.toJson(errors)))),
      input => {
        val user = User(3, input.name, input.email)
        Future.successful(Created(Json.toJson(user)))
      }
    )
  }
}

// conf/routes
GET     /health                     controllers.HomeController.health
GET     /api/users                  controllers.UserController.list
GET     /api/users/:id              controllers.UserController.get(id: Long)
POST    /api/users                  controllers.UserController.create

Production Configuration

# conf/application.conf
play.http.secret.key = ${?APPLICATION_SECRET}
play.server.http.port = ${?PORT}

# Database
db.default.driver = org.postgresql.Driver
db.default.url = ${?DATABASE_URL}
db.default.hikaricp.maximumPoolSize = 20

# Production settings
play.filters.enabled += "play.filters.gzip.GzipFilter"
play.filters.enabled += "play.filters.headers.SecurityHeadersFilter"

# Allowed hosts
play.filters.hosts.allowed = ["example.com", "localhost:9000"]

Building for Production

# Create production distribution
sbt dist
# Output: target/universal/play-scala-seed-1.0.zip

# Or create a Docker image
sbt Docker/publishLocal

# Copy to VPS and unzip
scp target/universal/play-scala-seed-1.0.zip user@vps:/opt/
ssh user@vps "cd /opt && unzip play-scala-seed-1.0.zip"

Running in Production

# Start with production settings
/opt/play-scala-seed-1.0/bin/play-scala-seed \
    -Dplay.http.secret.key="$(head -c 32 /dev/urandom | base64)" \
    -Dconfig.resource=production.conf \
    -J-Xmx512m \
    -J-Xms512m

Systemd Service

[Unit]
Description=Play Framework Application
After=network.target postgresql.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/play-app
ExecStart=/opt/play-app/bin/play-scala-seed -Dconfig.resource=production.conf -J-Xmx512m
EnvironmentFile=/opt/play-app/.env
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Docker Deployment

# build.sbt
dockerBaseImage := "eclipse-temurin:21-jre-alpine"
dockerExposedPorts := Seq(9000)
dockerUpdateLatest := true

# Build and run
sbt Docker/publishLocal
docker run -p 9000:9000 -e APPLICATION_SECRET=secret play-app:latest

Summary

Play Framework brings reactive programming to Scala web development with its non-blocking, stateless architecture. The combination of strong typing, pattern matching, and Akka actors provides a robust foundation for building scalable applications. sbt dist creates a self-contained deployment package, and the framework integrates naturally with the JVM ecosystem for enterprise environments. For Scala teams, Play remains the most mature and production-ready web framework option.

Was this article helpful?