1. Introduction

In this article, we will have a quick introduction to Spark framework. Spark framework is a rapid development web framework inspired by the Sinatra framework for Ruby and is built around Java 8 Lambda Expression philosophy, making it less verbose than most applications written in other Java frameworks.

It’s a good choice if you want to have a Node.js like experience when developing a web API or microservices in Java. With Spark, you can have a REST API ready to serve JSON in less than ten lines of code.

We will have a quick start with a “Hello World” example, followed by a simple REST API.

2. Maven Dependencies

2.1. Spark Framework

Include following Maven dependency in your pom.xml:

<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-core</artifactId>
    <version>2.5.4</version>
</dependency>

You can find the latest version of Spark on Maven Central.

2.2. Gson Library

At various places in the example, we will be using Gson library for JSON operations. To include Gson in your project, include this dependency in your pom.xml:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.0</version>
</dependency>

You can find the latest version of Gson on Maven Central.

3. Getting Started With Spark Framework

Let’s take a look at the basic building blocks of a Spark application and demonstrate a quick web service.

3.1. Routes

Web services in Spark Java are built upon routes and their handlers. Routes are essential elements in Spark. As per the documentation, each route is made up of three simple pieces – a verb, a path, and a callback.

  1. The verb is a method corresponding to an HTTP method. Verb methods include: get, post, put, delete, head, trace, connect, and options
  2. The path (also called a route pattern) determines which URI(s) the route should listen to and provide a response for
  3. The callback is a handler function that is invoked for a given verb and path in order to generate and return a response to the corresponding HTTP request. A callback takes a request object and response object as arguments

Here we show the basic structure for a route that uses the get verb:

get("/your-route-path/", (request, response) -> {
    // your callback code
});

3.2. Hello World API

Let’s create a simple web service that has two routes for GET requests and returns “Hello” messages in response. These routes use the get method, which is a static import from the class spark.Spark:

import static spark.Spark.*;

public class HelloWorldService {
    public static void main(String[] args) {
 
        get("/hello", (req, res)->"Hello, world");
        
        get("/hello/:name", (req,res)->{
            return "Hello, "+ req.params(":name");
        });
    }
}

The first argument to the get method is the path for the route. The first route contains a static path representing only a single URI (“/hello”).

The second route’s path (“/hello/:name”) contains a placeholder for the “name” parameter, as denoted by prefacing the parameter with a colon (“:”). This route will be invoked in response to GET requests to URIs such as “/hello/Joe” and “/hello/Mary”.

The second argument to the get method is a lambda expression giving a functional programming flavor to this framework.

The lambda expression has request and response as arguments and helps return the response. We will put our controller logic in the lambda expression for the REST API routes, as we shall see later in this tutorial.

3.3. Testing the Hello World API

After running the class HelloWorldService as a normal Java class, you will be able to access the service on its default port of 4567 using the routes defined with the get method above.

Let’s look at the request and response for the first route:

Request:

GET http://localhost:4567/hello

Response:

Hello, world

Let’s test the second route, passing the name parameter in its path:

Request:

GET http://localhost:4567/hello/baeldung

Response:

Hello, baeldung

See how the placement of the text “baeldung” in the URI was used to match the route pattern “/hello/:name” – causing the second route’s callback handler function to be invoked.

4. Designing a RESTful Service

In this section, we will design a simple REST web service for the following User entity:

public class User {
    private String id;
    private String firstName;
    private String lastName;
    private String email;

    // constructors, getters and setters
}

4.1. Routes

Let’s list the routes that make up our API:

  • GET /users — get list of all users
  • GET /users/:id — get user with given id
  • POST /users/:id — add a user
  • PUT /users/:id — edit a particular user
  • OPTIONS /users/:id — check whether a user exists with given id
  • DELETE /users/:id — delete a particular user

4.2. The User Service

Below is the UserService interface declaring the CRUD operations for the User entity:

public interface UserService {
 
    public void addUser (User user);
    
    public Collection<User> getUsers ();
    public User getUser (String id);
    
    public User editUser (User user) 
      throws UserException;
    
    public void deleteUser (String id);
    
    public boolean userExist (String id);
}

For demonstration purposes, we provide a Map implementation of this UserService interface in the GitHub code to simulate persistence. You can supply your own implementation with the database and persistence layer of your choice.

4.3. The JSON Response Structure

Below is the JSON structure of the responses used in our REST service:

{
    status: <STATUS>
    message: <TEXT-MESSAGE>
    data: <JSON-OBJECT>
}

The status field value can be either SUCCESS or ERROR. The data field will contain the JSON representation of the return data, such as a User or collection of Users.

When there is no data being returned, or if the status is ERROR, we will populate the message field to convey a reason for the error or lack of return data.

Let’s represent the above JSON structure using a Java class:

public class StandardResponse {
 
    private StatusResponse status;
    private String message;
    private JsonElement data;
    
    public StandardResponse(StatusResponse status) {
        // ...
    }
    public StandardResponse(StatusResponse status, String message) {
        // ...
    }
    public StandardResponse(StatusResponse status, JsonElement data) {
        // ...
    }
    
    // getters and setters
}

where StatusResponse is an enum defined as below:

public enum StatusResponse {
    SUCCESS ("Success"),
    ERROR ("Error");
 
    private String status;       
    // constructors, getters
}

5. Implementing RESTful Services

Now let’s implement the routes and handlers for our REST API.

5.1. Creating Controllers

The following Java class contains the routes for our API, including the verbs and paths and an outline of the handlers for each route:

public class SparkRestExample {
    public static void main(String[] args) {
        post("/users", (request, response) -> {
            //...
        });
        get("/users", (request, response) -> {
            //...
        });
        get("/users/:id", (request, response) -> {
            //...
        });
        put("/users/:id", (request, response) -> {
            //...
        });
        delete("/users/:id", (request, response) -> {
            //...
        });
        options("/users/:id", (request, response) -> {
            //...
        });
    }
}

We will show the full implementation of each route handler in the following subsections.

5.2. Add User

Below is the post method response handler which will add a User:

post("/users", (request, response) -> {
    response.type("application/json");
    User user = new Gson().fromJson(request.body(), User.class);
    userService.addUser(user);

    return new Gson()
      .toJson(new StandardResponse(StatusResponse.SUCCESS));
});

Note: In this example, the JSON representation of the User object is passed as the raw body of a POST request.

Let’s test the route:

Request:

POST http://localhost:4567/users
{
    "id": "1012", 
    "email": "[email protected]", 
    "firstName": "Mac",
    "lastName": "Mason1"
}

Response:

{
    "status":"SUCCESS"
}

5.3. Get All Users

Below is the get method response handler which returns all users from the UserService:

get("/users", (request, response) -> {
    response.type("application/json");
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS,new Gson()
        .toJsonTree(userService.getUsers())));
});

Now let’s test the route:

Request:

GET http://localhost:4567/users

Response:

{
    "status":"SUCCESS",
    "data":[
        {
            "id":"1014",
            "firstName":"John",
            "lastName":"Miller",
            "email":"[email protected]"
        },
        {
            "id":"1012",
            "firstName":"Mac",
            "lastName":"Mason1",
            "email":"[email protected]"
        }
    ]
}

5.4. Get User by Id

Below is the get method response handler which returns a User with the given id:

get("/users/:id", (request, response) -> {
    response.type("application/json");
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS,new Gson()
        .toJsonTree(userService.getUser(request.params(":id")))));
});

Now let’s test the route:

Request:

GET http://localhost:4567/users/1012

Response:

{
    "status":"SUCCESS",
    "data":{
        "id":"1012",
        "firstName":"Mac",
        "lastName":"Mason1",
        "email":"[email protected]"
    }
}

5.5. Edit a User

Below is the put method response handler, which edits the user having the id supplied in the route pattern:

put("/users/:id", (request, response) -> {
    response.type("application/json");
    User toEdit = new Gson().fromJson(request.body(), User.class);
    User editedUser = userService.editUser(toEdit);
            
    if (editedUser != null) {
        return new Gson().toJson(
          new StandardResponse(StatusResponse.SUCCESS,new Gson()
            .toJsonTree(editedUser)));
    } else {
        return new Gson().toJson(
          new StandardResponse(StatusResponse.ERROR,new Gson()
            .toJson("User not found or error in edit")));
    }
});

Note: In this example, the data are passed in the raw body of a POST request as a JSON object whose property names match the fields of the User object to be edited.

Let’s test the route:

Request:

PUT http://localhost:4567/users/1012
{
    "lastName": "Mason"
}

Response:

{
    "status":"SUCCESS",
    "data":{
        "id":"1012",
        "firstName":"Mac",
        "lastName":"Mason",
        "email":"[email protected]"
    }
}

5.6. Delete a User

Below is the delete method response handler, which will delete the User with the given id:

delete("/users/:id", (request, response) -> {
    response.type("application/json");
    userService.deleteUser(request.params(":id"));
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS, "user deleted"));
});

Now, let’s test the route:

Request:

DELETE http://localhost:4567/users/1012

Response:

{
    "status":"SUCCESS",
    "message":"user deleted"
}

5.7. Check if User Exists

The options method is a good choice for conditional checking. Below is the options method response handler which will check whether a User with the given id exists:

options("/users/:id", (request, response) -> {
    response.type("application/json");
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS, 
        (userService.userExist(
          request.params(":id"))) ? "User exists" : "User does not exists" ));
});

Now let’s test the route:

Request:

OPTIONS http://localhost:4567/users/1012

Response:

{
    "status":"SUCCESS",
    "message":"User exists"
}

6. Conclusion

In this article, we had a quick introduction to the Spark framework for rapid web development.

This framework is mainly promoted for generating microservices in Java. Node.js developers with Java knowledge who want to leverage libraries built on JVM libraries should feel at home using this framework.

And as always, you can find all the sources for this tutorial in the Github project.