====================================
This tutorial will help teach you how to build Java web applications with Play Framework 2.3.
Before you get started, you will need to install java, git, sbt, and finally, and older version of the Play Framework.
Many of these install may require you to update your PATH, which is done differently in Windows or the Mac. Those details are not provided in this tutorial.
Test that the activator
command works by running:
activator -help
You should see text displaying optional commands for the activator tool. If you do not, ensure you have that the downloaded activator script referenced in your PATH.
Alright! You are ready to go!
Create a new Play 2.3 application named "play2torial" by running:
activator new play2torial play-java-2.3
This will create a new project called play2torial
in your current working directory using the play-java-2.3 template (look here for more templates). Since the release of newer versions of the play framework, all versions of activator will try to use newer templates by default, so you'll have to specify the template manually to use an older version of Play.
In the newly created play2torial
directory create a new git repository by running:
git init
Add the files to the git repo and commit them:
git add .
git commit -m init
Throughout this tutorial you will be able to check your progress against the official tutorial. To do this add the official tutorial as a new git remote named upstream
by running:
git remote add upstream https://github.com/YogoGit/play2torial.git
Fetch the remote repository:
git fetch upstream
From this point forward, you will no longer be using activator, and instead you will be using sbt.
Before we take a tour of the app you can optionally generate project files for Eclipse. This is highly recommended.
For Eclipse run:
sbt eclipse
For IntelliJ, install the Scala IntelliJ Plugin and then open the project's build.sbt
file from the File > Open...
dialog.
Read more about IDE's
Now start the Play app by running:
sbt run
Open the following URL in your browser to verify the app is working:
http://localhost:9000/
Read more about the Play Console
Because this tutorial is based on an older version of Play, there are potential issues that must be worked-around. In the file 'build.sbt' in your project, please append the following line to the bottom of the file.
PlayKeys.playWatchService := play.sbtplugin.run.PlayWatchService.sbt(pollInterval.value)
Otherwise, you will see errors such as the following when you run the application.
java.lang.UnsatisfiedLinkError: no jnotify in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
...
Commit your changes to build.sbt:
git add build.sbt
git commit -m "Fixed JNotify issues"
git diff java-jnotify_fix
Play routes HTTP requests to a controller using the routes defined in the conf/routes
file. The routes file maps HTTP verbs and URL patterns to controller methods. The route that matched the request you just made was defined in the "routes" file with:
GET / controllers.Application.index()
That means that when a HTTP GET request for the URL /
comes in, it will be routed to the method named index
on the controllers.Application
class.
Add a new route to handle GET requests to /foo
with a call to controllers.Application.index()
by adding the following line to the conf/routes
file:
GET /foo controllers.Application.index()
Now try to open the following URL in your browser:
http://localhost:9000/foo
You should see the same welcome message as your did when you made the request to the /
URL.
Read more about Routing
Commit and verify your changes:
git add conf/routes
git commit -m "added new route"
git diff java-foo_route
Now that you have a new route for /foo
lets create a test for that route. Now create a new file test/FooTest.java
file containing:
import org.junit.Test;
import play.mvc.Result;
import play.test.Helpers;
import play.test.WithApplication;
import static play.test.Helpers.*;
import static org.fest.assertions.Assertions.*;
public class FooTest extends WithApplication {
@Test
public void testFooRoute() {
Result result = route(fakeRequest(GET, "/foo"));
assertThat(result).isNotNull();
}
}
That simple test will simulate a call to /foo
and validate that the result was not null. Run the test (you can keep the Play server running in another window):
sbt test
You should see something similiar to:
[info] Passed: Total 4, Failed 0, Errors 0, Passed 4
Read more about Testing Routes
Commit and verify your changes:
git add test
git commit -m "added test for foo route"
git diff java-test_route
The app
directory is the main source for a Play app. In that directory there is a directory named controllers
containing a file named Application.java
which is the controllers.Application
class.
The index()
method body looks like:
return ok(index.render("Your new application is ready."));
Edit the Application.java
file and change the Your new application is ready.
string to hello, world
. Save the file and then reload the following URL in your browser:
http://localhost:9000/
Notice that the header at the top of the page now reads hello, world
. Play recompiled the Java controller behind the scenes. If you had made a change that could not be compiled you would see the compile error in your browser and in your console.
This change will break two of the existing tests so change the Your new application is ready.
string to hello, world
in the test/ApplicationTest.java
and test/IntegrationTest.java
files.
To verify the tests pass, run:
sbt test
Commit and verify that your changes:
git commit -am "updated controller"
git diff java-hello_controller
You can do tests against a controller by simply creating a new JUnit Test. Add a new test method to the test/ApplicationTest.java
file:
@Test
public void testIndex() {
Result result = controllers.Application.index();
assertThat(status(result)).isEqualTo(OK);
assertThat(contentType(result)).isEqualTo("text/html");
assertThat(charset(result)).isEqualTo("utf-8");
assertThat(contentAsString(result)).contains("hello, world");
}
This simulates a request to the Application.index()
controller method and verifies that the response is what we expect. Run the test:
sbt test
You should see something like:
[info] Passed: Total 5, Failed 0, Errors 0, Passed 5
Read more about Testing Controllers
Commit and verify that your changes:
git add test/ApplicationTest.java
git commit -m "added Application controller test"
git diff java-test_controller
Play uses Scala for server-side templating. The Application
controller renders the views.html.index
template which is compiled from the app/views/index.scala.html
file. The index
template takes a String parameter:
@(message: String)
Then the index
template uses the main
template (from app/views/main.scala.html
) to get a base HTML page:
@main("Welcome to Play") {
The main template is passed a String parameter for the page title and a Html parameter for the body of the page. The body of the page is the Play welcome message which comes from:
@play20.welcome(message, style = "Java")
Change the Play welcome message to uppercase:
@play20.welcome(message.toUpperCase, style = "Java")
View your changes in the browser:
http://localhost:9000/
Because the test/ApplicationTest.java
and test/IntegrationTest.java
tests use this updated template, update the tests to account for the upper cased string by changing all hello, world
strings to HELLO, WORLD
.
As you can see, the test/ApplicationTest.java
file tests the server-side template directly:
@Test
public void renderTemplate() {
Content html = views.html.index.render("HELLO, WORLD");
assertThat(contentType(html)).isEqualTo("text/html");
assertThat(contentAsString(html)).contains("HELLO, WORLD");
}
The test/IntegrationTest.java
does an integration test by starting a Play server and using HtmlUnit to make a headless browser request to the server, thus testing the routes, controller, and views:
@Test
public void test() {
running(testServer(3333, fakeApplication(inMemoryDatabase())), HTMLUNIT, new Callback<TestBrowser>() {
public void invoke(TestBrowser browser) {
browser.goTo("http://localhost:3333");
assertThat(browser.pageSource()).contains("HELLO, WORLD");
}
});
}
Run the tests and verify they all pass:
sbt test
Read more about Templating and Template Use Cases
Commit and verify that your changes:
git commit -am "change the view"
git diff java-hello_view
By default, Play 2.3 with Java uses Ebean for RDBMS persistence. To setup a data source, edit the conf/application.conf
file and uncomment these lines:
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
ebean.default="models.*"
This will use an in-memory database for a data source named "default".
Create a new Java class to hold Task
objects in a package named models
by creating a new app/models/Task.java
file containing:
package models;
import play.db.ebean.Model;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Task extends Model {
@Id
public String id;
public String contents;
}
Create a test for your model by creating a new test/TaskTest.java
file containing:
import org.junit.Test;
import static play.test.Helpers.fakeApplication;
import static play.test.Helpers.running;
import static org.fest.assertions.Assertions.assertThat;
import models.Task;
public class TaskTest {
@Test
public void create() {
running(fakeApplication(), new Runnable() {
public void run() {
Task task = new Task();
task.contents = "Write a test";
task.save();
assertThat(task.id).isNotNull();
}
});
}
}
Run the tests and make sure all four pass:
sbt test
Ebean will automatically create a new database evolution script for you in a file named conf/evolutions/default/1.sql
.
Read more about EBean ORM Models
Commit and verify your changes:
git add conf/evolutions conf/application.conf app/models/Task.java test/TaskTest.java
git commit -m "added Task model, in-memory database config, and test"
git diff java-task_model
Start by adding a new method to the app/controllers/Application.java
file that will map request parameters to a Task
object, save it, and then redirect to the index()
page.
Now add a new method named addTask
:
public static Result addTask() {
play.data.Form<models.Task> form = play.data.Form.form(models.Task.class).bindFromRequest();
models.Task task = form.get();
task.save();
return redirect(routes.Application.index());
}
Now add a new route to the conf/routes
file that will handle POST
requests to /task
and handle the request with the Application.addTask()
method:
POST /task controllers.Application.addTask()
Now create a form in the app/views/index.scala.html
template for adding new Tasks
. Replace the @play20.welcome
line with:
@message.toUpperCase
@helper.form(action = routes.Application.addTask()) {
<input name="contents"/>
<input type="submit"/>
}
Test out the new UI by loading the app in your browser:
http://localhost:9000/
You will be prompted to run the database evolutions. Just click the Apply this script now!
button.
Add a new Task
and the browser should refresh the index page.
Read more about Forms and Templates
Commit and verify your changes:
git commit -am "add new UI to create Tasks"
git diff java-task_add
Create a new getTasks
controller method in the app/controllers/Application.java
file:
public static Result getTasks() {
java.util.List<models.Task> tasks = new play.db.ebean.Model.Finder(String.class, models.Task.class).all();
return ok(play.libs.Json.toJson(tasks));
}
Add a new route to the conf/routes
file to get the tasks as JSON serialized data:
GET /tasks controllers.Application.getTasks()
After adding a new Task
load the following URL in your browser:
http://localhost:9000/tasks
Verify that you see a Task (or Tasks) in JSON form. If the contents
property in the JSON is null, then stop the sbt run
process, run sbt clean
and then run the app again: sbt run
Commit and verify your changes:
git commit -am "get the tasks as JSON"
git diff java-task_json
Add the jQuery WebJar to your project by adding the following to your
build.sbt
file after the first libraryDependencies declaration.
libraryDependencies += "org.webjars" % "jquery" % "1.11.2"
Note: Make sure you have an empty line before the line above.
Restart the sbt run
process to reload the dependencies.
In the body of the app/views/index.scala.html
file add a place to display the tasks above the form:
<ul id="tasks"></ul>
Create a new file named app/assets/javascripts/index.coffee
(and create the necessary directories) containing a simple CoffeeScript application that uses jQuery to load and display the tasks:
$ ->
$.get "/tasks", (data) ->
$.each data, (index, task) ->
$("#tasks").append $("<li>").text task.contents
This makes a get
request to /tasks
for the JSON serialized list of Task
objects and then adds a new list item to the page element with the id of tasks
for each Task
.
Update the app/views/main.scala.html
file to load jQuery from the WebJar and include the compiled version of the index.coffee
JavaScript:
<script src="@routes.Assets.at("lib/jquery/jquery.min.js")" type="text/javascript"></script>
<script src="@routes.Assets.at("javascripts/index.js")" type="text/javascript"></script>
Check out the app in your browser and verify that the list of tasks is displayed:
http://localhost:9000/
To see the compiled version of the compiled javascript you can go to http://localhost:9000/assets/javascripts/index.js
Read more about Assets and CoffeeScript
Commit and verify your changes:
git add app/views/index.scala.html app/assets/javascripts/index.coffee app/views/main.scala.html
git commit -m "Display the list of tasks using jQuery and CoffeeScript"
git diff java-task_coffeescript
Bootstrap is a CSS library that makes it easy to make a web app look better. To use Bootstrap start by adding the dependency to the build.sbt
file:
libraryDependencies += "org.webjars" % "bootstrap" % "2.1.1"
Note: Make sure you have an empty line before the line above.
Now restart the Play server so that it will fetch the new dependency. To use Bootstrap simply include the following line in the app/views/main.scala.html
file, making sure it is between the <title>
and the main.css
lines:
<link rel="stylesheet" media="screen" href="@routes.Assets.at("lib/bootstrap/css/bootstrap.min.css")">
Add the following to the public/stylesheets/main.css
file in order to move the main content down to a viewable location:
body {
padding-top: 50px;
}
Create a new template component that will be used to create new Bootstrap-friendly form fields by creating a new file named app/views/twitterBootstrapInput.scala.html
containing:
@(elements: helper.FieldElements)
<div class="control-group @if(elements.hasErrors) {error}">
<label for="@elements.id" class="control-label">@elements.label</label>
<div class="controls">
@elements.input
<span class="help-inline">@elements.errors.mkString(", ")</span>
</div>
</div>
Update the app/views/index.scala.html
file to use Bootstrap for a nice header, better layout, and nicer default fonts:
@(message: String, taskForm: Form[Task])
@implicitFieldConstructor = @{ helper.FieldConstructor(twitterBootstrapInput.render) }
@main("Welcome to Play") {
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<a id="titleLink" class="brand" href="/">@message.toUpperCase</a>
</div>
</div>
</div>
<div class="container">
<ul id="tasks"></ul>
@helper.form(action = routes.Application.addTask(), 'class -> "well form-horizontal") {
@helper.inputText(taskForm("contents"), '_label -> "Contents")
<div class="controls">
<input type="submit" class="btn btn-primary"/>
</div>
}
</div>
}
The template now takes a second parameter that is a Form[Task]
and must be passed to the template. This will be used for helping with form validation. Update the index()
method in the app/controllers/Application.java
file to pass the new required parameter:
public static Result index() {
return ok(index.render("hello, world", play.data.Form.form(models.Task.class)));
}
In the test/ApplicationTest.java
file, update the renderTemplate
test method:
Content html = views.html.index.render("HELLO, WORLD", play.data.Form.form(models.Task.class));
Run the tests to make sure they still pass:
sbt test
Load the app in your browser and verify it still works:
http://localhost:9000/
Read more about Templates
Commit and verify your changes:
git add app/views/twitterBootstrapInput.scala.html
git commit -m "Add Bootstrap"
git diff java-bootstrap
Add a simple Required
constraint to contents
property on the app/models/Task.java
class:
@play.data.validation.Constraints.Required
public String contents;
Update the addTask
method on the app/controllers/Application.java
controller to check for form errors and if it sees any then render the form instead of trying to save the Task:
public static Result addTask() {
play.data.Form<models.Task> form = play.data.Form.form(models.Task.class).bindFromRequest();
if (form.hasErrors()) {
return badRequest(index.render("hello, world", form));
}
else {
models.Task task = form.get();
task.save();
return redirect(routes.Application.index());
}
}
Load the app in your browser verify that adding an empty Task displays an error:
http://localhost:9000/
Read more about Validation
Commit and verify your changes:
git commit -am "Add validation"
git diff java-validation
You've built a Play 2.3 app and ran it locally. You've also learned how to get started with Play 2.3, Ebean, CoffeeScript, Twitter Bootstrap, jQuery, and RESTful JSON services. Have fun as you continue to learn Play 2.3!