DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Building REST API Backend Easily With Ballerina Language
  • Aggregating REST APIs Calls Using Apache Camel
  • User-Friendly API Publishing and Testing With Retrofit
  • How To Check for JSON Insecure Deserialization (JID) Attacks With Java

Trending

  • Secure by Design: Modernizing Authentication With Centralized Access and Adaptive Signals
  • Operational Principles, Architecture, Benefits, and Limitations of Artificial Intelligence Large Language Models
  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  • Breaking Bottlenecks: Applying the Theory of Constraints to Software Development
  1. DZone
  2. Coding
  3. Languages
  4. Building a Simple RESTful API with Java Spark

Building a Simple RESTful API with Java Spark

By 
Michael Scharhag user avatar
Michael Scharhag
·
Jun. 09, 14 · Interview
Likes (3)
Comment
Save
Tweet
Share
111.0K Views

Join the DZone community and get the full member experience.

Join For Free

Disclaimer: This post is about the Java micro web framework named Spark and not about the data processing engine Apache Spark.

In this blog post we will see how Spark can be used to build a simple web service. As mentioned in the disclaimer, Spark is a micro web framework for Java inspired by the Ruby framework Sinatra. Spark aims for simplicity and provides only a minimal set of features. However, it provides everything needed to build a web application in a few lines of Java code.

Getting Started

Let's assume we have a simple domain class with a few properties and a service that provides some basic CRUDfunctionality:

public class User {
private String id;
private String name;
private String email;

// getter/setter
}
public class UserService {

// returns a list of all users
public List<User> getAllUsers() { .. }

// returns a single user by id
public User getUser(String id) { .. }

// creates a new user
public User createUser(String name, String email) { .. }

// updates an existing user
public User updateUser(String id, String name, String email) { .. }
}

We now want to expose the functionality of UserService as a RESTful API (For simplicity we will skip the hypermedia part of REST ;-)). For accessing, creating and updating user objects we want to use following URL patterns: 

GET/usersGet a list of all users
GET/users/<id>Get a specific user
POST/usersCreate a new user
PUT/users/<id>Update a user

The returned data should be in JSON format.

To get started with Spark we need the following Maven dependencies:

<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
</dependency>

Spark uses SLF4J for logging, so we need to a SLF4J binder to see log and error messages. In this example we use the slf4j-simple dependency for this purpose. However, you can also use Log4j or any other binder you like. Having slf4j-simple in the classpath is enough to see log output in the console.
We will also use GSON for generating JSON output and JUnit to write a simple integration tests. You can find these dependencies in the complete pom.xml.

Returning All Users 

Now it is time to create a class that is responsible for handling incoming requests. We start by implementing the GET /users request that should return a list of all users. 

import static spark.Spark.*;
public class UserController {
public UserController(final UserService userService) {
get("/users", new Route() {
@Override
public Object handle(Request request, Response response) {
// process request
return userService.getAllUsers();
}
});

// more routes
}
}

Note the static import of spark.Spark.* in the first line. This gives us access to various static methods including get(), post(), put() and more. Within the constructor the get() method is used to register aRoute that listens for GET requests on /users. A Route is responsible for processing requests. Whenever aGET /users request is made, the handle() method will be called. Inside handle() we return an object that should be sent to the client (in this case a list of all users).

Spark highly benefits from Java 8 Lambda expressions. Route is a functional interface (it contains only one method), so we can implement it using a Java 8 Lambda expression. Using a Lambda expression the Routedefinition from above looks like this:

get("/users", (req, res) -> userService.getAllUsers());

To start the application we have to create a simple main() method. Inside main() we create an instance of our service and pass it to our newly created UserController:

public class Main {
public static void main(String[] args) {
new UserController(new UserService());
}
}

If we now run main(), Spark will start an embedded Jetty server that listens on Port 4567. We can test our first route by initiating a GET http://localhost:4567/users request. 
In case the service returns a list with two user objects the response body might look like this: 

[com.mscharhag.sparkdemo.User@449c23fd, com.mscharhag.sparkdemo.User@437b26fe]

Obviously this is not the response we want.

Spark uses an interface called ResponseTransformer to convert objects returned by routes to an actual HTTP response. ReponseTransformer looks like this:

public interface ResponseTransformer {
String render(Object model) throws Exception;
}

ResponseTransformer has a single method that takes an object and returns a String representation of this object. The default implementation of ResponseTransformer simply calls toString() on the passed object (which creates output like shown above).

Since we want to return JSON we have to create a ResponseTransformer that converts the passed objects to JSON. We use a small JsonUtil class with two static methods for this:

public class JsonUtil {
public static String toJson(Object object) {
return new Gson().toJson(object);
}

public static ResponseTransformer json() {
return JsonUtil::toJson;
}
}

toJson() is an universal method that converts an object to JSON using GSON. The second method makes use of Java 8 method references to return a ResponseTransformer instance. ResponseTransformer is again a functional interface, so it can be satisfied by providing an appropriate method implementation (toJson()). So whenever we call json() we get a new ResponseTransformer that makes use of our toJson()method.

In our UserController we can pass a ResponseTransformer as a third argument to Spark's get()method:

import static com.mscharhag.sparkdemo.JsonUtil.*;
public class UserController {
public UserController(final UserService userService) {
get("/users", (req, res) -> userService.getAllUsers(), json());
...
}
}

Note again the static import of JsonUtil.* in the first line. This gives us the option to create a newResponseTransformer by simply calling json().
Our response looks now like this:

[{
"id": "1866d959-4a52-4409-afc8-4f09896f38b2",
"name": "john",
"email": "john@foobar.com"
},{
"id": "90d965ad-5bdf-455d-9808-c38b72a5181a",
"name": "anna",
"email": "anna@foobar.com"
}]

We still have a small problem. The response is returned with the wrong Content-Type. To fix this, we can register a Filter that sets the JSON Content-Type:

after((req, res) -> {
res.type("application/json");
});

Filter is again a functional interface and can therefore be implemented by a short Lambda expression. After a request is handled by our Route, the filter changes the Content-Type of every response toapplication/json. We can also use before() instead of after() to register a filter. Then, the Filterwould be called before the request is processed by the Route.

The GET /users request should be working now :-) 

Returning a Specific User

To return a specific user we simply create a new route in our UserController:

get("/users/:id", (req, res) -> {
String id = req.params(":id");
User user = userService.getUser(id);
if (user != null) {
return user;
}
res.status(400);
return new ResponseError("No user with id '%s' found", id);
}, json());

With req.params(":id") we can obtain the :id path parameter from the URL. We pass this parameter to our service to get the corresponding user object. We assume the service returns null if no user with the passed id is found. In this case, we change the HTTP status code to 400 (Bad Request) and return an error object.

ResponseError is a small helper class we use to convert error messages and exceptions to JSON. It looks like this:

public class ResponseError {
private String message;

public ResponseError(String message, String... args) {
this.message = String.format(message, args);
}

public ResponseError(Exception e) {
this.message = e.getMessage();
}

public String getMessage() {
return this.message;
}
}

We are now able to query for a single user with a request like this:
GET /users/5f45a4ff-35a7-47e8-b731-4339c84962be
If an user with this id exists we will get a response that looks somehow like this:

{
"id": "5f45a4ff-35a7-47e8-b731-4339c84962be",
"name": "john",
"email": "john@foobar.com"
}

If we use an invalid user id, a ResponseError object will be created and converted to JSON. In this case the response looks like this:

{
"message": "No user with id 'foo' found"
}


Creating and Updating Users

Creating and updating users is again very easy. Like returning the list of all users it is done using a single service call:

post("/users", (req, res) -> userService.createUser(
req.queryParams("name"),
req.queryParams("email")
), json());

put("/users/:id", (req, res) -> userService.updateUser(
req.params(":id"),
req.queryParams("name"),
req.queryParams("email")
), json());

To register a route for HTTP POST or PUT requests we simply use the static post() and put() methods of Spark. Inside a Route we can access HTTP POST parameters using req.queryParams().
For simplicity reasons (and to show another Spark feature) we do not do any validation inside the routes. Instead we assume that the service will throw an IllegalArgumentException if we pass in invalid values.

Spark gives us the option to register ExceptionHandlers. An ExceptionHandler will be called if anException is thrown while processing a route. ExceptionHandler is another single method interface we can implement using a Java 8 Lambda expression:

exception(IllegalArgumentException.class, (e, req, res) -> {
res.status(400);
res.body(toJson(new ResponseError(e)));
});

Here we create an ExceptionHandler that is called if an IllegalArgumentException is thrown. The caught Exception object is passed as the first parameter. We set the response code to 400 and add an error message to the response body.

If the service throws an IllegalArgumentException when the email parameter is empty, we might get a response like this:

{
"message": "Parameter 'email' cannot be empty"
}


The complete source the controller can be found here.

Testing

Because of Spark's simple nature it is very easy to write integration tests for our sample application.
Let's start with this basic JUnit test setup:

public class UserControllerIntegrationTest {
@BeforeClass
public static void beforeClass() {
Main.main(null);
}

@AfterClass
public static void afterClass() {
Spark.stop();
}

...
}

In beforeClass() we start our application by simply running the main() method. After all tests finished we call Spark.stop(). This stops the embedded server that runs our application.

After that we can send HTTP requests within test methods and validate that our application returns the correct response. A simple test that sends a request to create a new user can look like this:

@Test
public void aNewUserShouldBeCreated() {
TestResponse res = request("POST", "/users?name=john&email=john@foobar.com");
Map<String, String> json = res.json();
assertEquals(200, res.status);
assertEquals("john", json.get("name"));
assertEquals("john@foobar.com", json.get("email"));
assertNotNull(json.get("id"));
}

request() and TestResponse are two small self made test utilities. request() sends a HTTP request to the passed URL and returns a TestResponse instance. TestResponse is just a small wrapper around some HTTP response data. The source of request() and TestResponse is included in the complete test classfound on GitHub.

Conclusion

Compared to other web frameworks Spark provides only a small amount of features. However, it is so simple you can build small web applications within a few minutes (even if you have not used Spark before). If you want to look into Spark you should clearly use Java 8, which reduces the amount of code you have to write a lot.

You can find the complete source of the sample project on GitHub. 


API Java (programming language) Requests REST Web Protocols Object (computer science) Web Service application Testing JSON

Published at DZone with permission of Michael Scharhag, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Building REST API Backend Easily With Ballerina Language
  • Aggregating REST APIs Calls Using Apache Camel
  • User-Friendly API Publishing and Testing With Retrofit
  • How To Check for JSON Insecure Deserialization (JID) Attacks With Java

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: