The other day I was supporting a client in my day job by helping him use Postman to generate HTTP Post requests. The problem was, that his language of choice was Dart, a language I had never heard of, much less used. I’ve worked with Postman for quite some time, but I never clicked this discrete link to the right of the request.
Imagine my surprise when I clicked that link and a code snippet showing the Dart code.
It generates cURL requests,
Python code snippets,
Node.js code snippets,
and many more. For example, Postman can generate snippets for over 30 different languages or frameworks.
How to Generate Python Code Snippets
Create the request you desire, for example, the following is a simple GET request listing available APIs on the API Guru website.
Click the small Code generate button.
Select your desired language or framework.
Click the copy icon to copy the code snippet to your clipboard.
Here’s a video by a member of the Postman team showing how to use the code snippet generation feature.
I’ve been working as the Developer Evangelist for DynamicPDF for some time now. As well as our desktop/server product, we offer a cloud version of our software: DynamicPDF Cloud API. It’s a powerful cloud API for creating and manipulating PDFs for your business. In this post, I outline some of its features and why you should use it if you create PDFs for your organization.
The cloud API is built on the powerful DynamicPDF Core Suite, a software platform that has been used for several decades by many of the world’s largest corporations.
Returns a PDF after performing one of the PDF endpoint’s tasks (page, dlex, html, word, image) or merging.
REST Endpoint documentation
DynamicPDF Designer Online
DynamiciPDF Cloud API also offers the following client libraries to make using the endpoints easier.
C#
Java
Node.js
PHP
Go
Python
It also offers – arguably the most powerful tool available anywhere online – DynamicPDF Designer Online, a graphical tool for creating rich PDF documents using your organization’s business data.
The API is free to try, and the documentation and support are top-notch. I should know – I wrote most the documentation, tutorials, and example code. There are tons of tutorials, and our support is great.
In this tutorial we use the Amazon Web Services Java 2 Application Programming Interface (API) to create a Rest application using Spring Boot that reads and writes to a DynamoDB database. This tutorial assumes AWS familiarity, Java programming experience, and Spring Boot experience. However, even without this experience, this tutorial should still prove useful, as it provides considerable supplementary resources for you to review. If you want to learn the AWS DynamoDB Java API then this tutorial is for you.
Here we create a simple database consisting of “observation stations” and “observations” gathered via a camera. Whatever…suspend disbelief and just go with it. Now, suppose, the stations require a means of uploading observations to an associated DynamoDB table. We decide upon a Rest API for stations to upload data. We implement this API using a Spring Boot Rest application. Again, if this all sounds suspect, suspend disbelief and focus on the AWS code and not the application design.
In this tutorial we,
create two database tables using the DynamoDB console,
create a couple items using the console,
create an IAM programatic user,
create a Spring Boot application that provides Rest endpoints so a client application can,
write an observation,
read an observation,
update an observation,
delete an observation,
batch write multiple observations,
conditionally query for a station’s observations,
and conditionally update observations,
and test the Rest endpoints using Postman.
This tutorial’s purpose is to explore the DynamoDB, not introduce Spring Boot, Rest, or JSON and assumes basic knowledge of all three. However, if new to any of these topics, links are provided to learn them before continuing.
NoSQL Databases
DynamoDB is a key-value and document NoSQL database. If unfamiliar with NoSQL Document databases, you should familiarize yourself before continuing. The following is an introductory video introducing NoSQL Databases.
The following are two good written introductory articles covering NoSQL and DynamoDB.
Note that Amazon also offers DocumentDB, which we could use as an alternative to DynamoDB. However, DocumentDB will be covered in a different tutorial.
A DynamoDB database can be described as the following. Tables consist of items. An item has one or more attributes. In a table you define the partition key and optionally define a sort key. The partition key is a key-value pair that not only uniquely identifies an item, it determines how the item is distributed on a computer’s storage. A sort key not only logically sorts items, it stores the items accordingly. Obviously, there is more to NoSQL physical storage and how it achieves its scalability, but that is beyond this tutorial’s scope.
Amazon Web Services & DynamoDB
Amazon DynamoDB is a NoSQL key-value and document database offered as a cloud service. It is fully managed and allows users to avoid the administrative tasks associated with hosting an enterprise database. As with almost all Amazon’s offerings, it is accessible via a Rest API.
Amazon offers software development kits (SDKs) to simplify working with the Rest API. The languages offered are Java, C++, C#, Ruby, Python, JavaScript, NodeJs, PHP, Objective-C, and Go. In this article we use the Java API. There are currently two versions of the API, in this tutorial we use the Java 2 SDK.
The Java 2 AWS SDK is a rewrite of the Java 1.1 AWS SDK and changes from a more traditional programming paradigm of instantiating objects using constructors and then setting properties using setters to a fluent interface/builder programming style.
Fluent Interface
The fluent interface is a term created by Martin Fowler and Eric Evans. It refers to an programming style where the public methods (the API) can be chained together to perform a task. It is used by the AWS Java SDK 2.0 when using builders. The builder tasks perform tasks but then return an instance of the builder. This allows chaining methods together. For more information on the fluid interface and builders, refer to this blog post: Another builder pattern for Java.
DynamoDB Low-Level API
As with all AWS APIs, DynamoDB is exposed via Rest endpoints. The AWS SDKs provide an abstraction layer freeing you from calling Rest directly. Above that layer, the Java SDK provides a class named DynamoDBMapper that allows working with DynamoDB similarly to the Java Persistence Framework (JPA). Although useful, using the lower-level API is not that difficult. Moreover, there are many situations where you would not wish to create a dependency in your object model that relies on DynamoDB.
For example, suppose we implemented a system that stored widgets in DynamoDB. If using the DynamoDBMapper, the Widget model class would be dependent upon DynamoDB via annotations mapping the class to the Widgets table.
Alternatively, if we do not wish to use the DynamoDBMapper we can implement something similar to the following diagram. It is a typical DAO pattern, where the only direct dependency upon the AWS SDK is the WidgetDaoImpl class. For more information on the DAO design pattern, refer to the following introductory article: DAO Design Pattern.
In this tutorial on the AWS DynamoDB Java APl, we use the SDKs direct calls to the underlying DynamoDB Rest endpoints. As an aside, note that we do not use the DAO design pattern, instead putting the data access logic directly in the controller class for brevity. We do, however, use the Spring MVC design pattern using Rest.
Imagine we have stations responsible for taking photo observations. A station has a coordinate, address, and a name. A station has one Coordinate. A station has one address. A station can have unlimited observations.
Although this tutorial does not discuss NoSQL database design, from the diagram below it seems reasonable we need two tables, Station and Observation. Moreover, as the Observation table is very write intensive – stations will be sending observations to the application on a continuous basis – it makes sense to not include observations as a collection within a Station instance but keep it as a separate table. Remember, these are JSON documents, not relational tables. It is unreasonable to design Observations as a list of items within a Station and would lead to an excessively large and unwieldy database.
If there were enough Stations, for even more efficiency we might create a separate table for each station’s observations. This would allow greater throughput for both writing and reading observations. But, in this tutorial we simply define a stationid to identify an observation’s station and will create an index on this value.
DynamoDB Console
The AWS Management Console provides an easy web-based way of working with Amazon’s cloud services. Although not covered in this tutorial, for those new to AWS, here is a short video by Amazon explaining the Management Console. Note that AWS also offers a command-line interface and Application Programming Interfaces (APIs) for accessing its cloud services.
AWS Essentials: How to Navigate the AWS Console by LinuxAcademy.
Before beginning the programming portion of this tutorial we must create the DynamoDB database.
Create Station Table
After entering the AWS Management Console, navigate to the DynamoDB console.
Click the Create table button.
Provide Station as the table’s name and id as the table’s primary key.
Creating Station Items
Remember, DynamoDB is schema-less. We create an item but do not define a table’s schema. Instead, we create a couple items with the desired structure.
Click the Items tab and click the Create Item button.
Create an id and name attribute, assigning id as a Number datatype and name as a String. Assign the values 221 and “Potomac Falls” respectively.
Create an attribute named address and assign it the Map datatype.
Add a city, street, and zip attribute as String datatypes to the address map. In the example below, I assigned Potomac, 230 Falls Street, and 22333 as the attribute values.
Create coordinate as a Map and assign it a latitude and longitude attribute as Number datatypes. I assigned the values 38.993465 and -77.249247 as the latitude and longitude values.
Repeat for one more station.
We created two items in the Station table. Here are the two items as JSON.
You can view the JSON after creating an item by selecting the item and then selecting the text view in the popup.
Note that the preceding JSON document is generic JSON. The actual JSON, as stored by DynamoDB (including datatypes), is as follows. Where the M, S, N, SS, etc. represent the element datatypes.
For example, in the following JSON document an observation’s address and coordinate are both Map datatypes, the city, street, zip are String datatypes, and the latitude and longitude are Number datatypes.
You can toggle between JSON and DynamoDB JSON in the popup window, as the following illustrates (note the DynamoDB JSON checkbox).
Create Observation Table
After creating the Station table we need to create the Observation table.
Create a new table named Observation.
Assign it a partition key of id and a sort key of stationid.
Composite Key (Partition Key & Sort Key)
The partition key is a table’s primary key and consists of a single attribute. DynamoDB uses this key to create a hash that determines the item’s storage. When used alone, the partition key uniquely identifies an item, as no two items can have the same partition key. However, when also defining a sort key, one or more items can have the same partition key, provided the partition key combined with the sort key is unique. Think of it as a compound key.
The Sort key helps DynamoDB more effectively store items, as it groups items with the same sort key together (hence the name sort key, as it sorts the items using this key).
An observation should have an id that identifies it and observations should be sorted by station, so we defined a stationid as the table’s sort key.
Create Sample Observations
As with the Station table, create some Observation items rather than define a schema.
Find three images, of small size, to use for this project. If you wish, use the three sample images from this tutorial’s Git project.
Or, if you wish, simply use the JSON sampleData.json file provided in this tutorial’s Git project.
The following is a JSON list of four observations. The image base64 string is truncated so it can be easily displayed here. You can obtain the original file, named observations.json, from this tutorial’s Git project.
Images are binary. However, all binary can be represented by a String provided it is encoded and decoded correctly. Base64 is an encoding scheme that is converts binary to a string. It’s useful because it allows embedding binary data, such as an image, in a textual file, such as a webpage or JSON document. DynamoDB uses Base64 format to encode binary data to strings when transporting data and decode strings to binary data when storing the data. Therefore, the image sent to the Rest endpoints we create should be base64 encoded.
Create IAM Application User
Before beginning the Spring Boot application, we need a user with programatic access to the AWS DynamoDB API. If you are unfamiliar with IAM, the following introductory video should prove helpful. Otherwise, let’s create a user.
Navigate to the IAM Console and click Add user.
Create a new user named DynamoDBUser.
Assign DynamoDBUser with Programmatic access.
Create a new group named dynamo_users with AmazonDynamoDBFullAccess.
Assign DynamoDBUser to the dynamo_users group.
If you created the user correctly, you should see the following Summary screen.
Save the credentials file, credentials.csv, to your local hard-drive.
Spring Boot Application
Now that we have created the two needed tables and created a user we can begin the sample application. We create a Rest API for stations to save, retrieve, update, and delete observations. Not much explanation is devoted to Spring Boot, so if you have never created a Spring Boot Rest application you might consider completing a tutorial or two on Spring Boot and Rest. The following are links to two tutorials; however, there are many more on the web.
In the POM we define the AWS Bill of Materials (BOM) and the required AWS libraries. Note that when using a BOM it is unnecessary to specify the library versions, as the BOM manages versions. We also define the Spring Boot libraries required.
Create an application.properties file in the resources folder. Open credentials.csv and add the credentials to the file with the following property names.
NOTE: THIS USER WAS DELETED BEFORE PUBLISHING THIS TUTORIAL.
Create a new class named SiteMonitorApplication in the com.tutorial.aws.dynamodb.application package.
Annotate the class with @SpringBootApplication annotation.
Create the main method and have it launch the Spring Boot application.
package com.tutorial.aws.dynamodb.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.tutorial.aws.dynamodb" })
public class SiteMonitorApplication {
public static void main(String[] args) {
SpringApplication.run(SiteMonitorApplication.class, args);
}
}
Create Observation Data Object
Create a class named Observation in the com.tutorial.aws.dynamodb.model package.
Create variables with the same names and types as in the JSON data created above.
package com.tutorial.aws.dynamodb.model;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
public class Observation {
private long stationid;
private String date;
private String time;
private String image;
private List<String> tags;
public long getStationid() {
return stationid;
}
public void setStationid(long stationid) {
this.stationid = stationid;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
public List<String> getTags() {
return this.tags;
}
@Override
public String toString() {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
The Observation object’s attributes are the same as in the JSON Observation document. Notice in the toString method we used an ObjectMapper from the Jackson library. We did not include this library in our POM, as the spring-boot-starter-web library includes this library.
The ObjectMapper maps JSON to Objects and Objects to JSON. It is how Spring Rest accomplishes this task. In the toString method we are telling the ObjectMapper instance to write the Observation object as a JSON string. For more on the ObjectMapper, here is a tutorial that explains the class in more depth: Jackson ObjectMapper.
Create Rest Controller
The Rest Controller provides the external API visible to Stations to send data to our application. Through the API, client applications will transmit data to the DynamoDB database. Different stations can develop its own client application in any language that supports Rest. The only requirement is that the station’s data follows the expected JSON format.
Note: we are violating the MVC Design pattern by putting data access directly in the Controller. Suspend disbelieve and ignore this anti-pattern.
Let’s create a Rest Controller to define our application’s API.
Create a class named ObservationApiController in the com.tutorial.aws.dynamodb.api package and annotate it with the @RestController annotation.
Assign it a top-level path of /observations.
Create a Rest endpoint for uploading a new Observation. Assign it the /observation mapping and name the method createObservation.
Have the method take an Observation as the request’s body.
Have the method print the uploaded Observation to the command-line.
package com.tutorial.aws.dynamodb.api;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tutorial.aws.dynamodb.model.Observation;
@RestController
@RequestMapping(value = "/observations")
public class ObservationApiController {
@PostMapping("/observation")
public void createObservation(@RequestBody Observation
observation) {
System.out.println(observation.toString());
}
}
Compile the application using Maven and start the application.
After the application starts, we can test using Postman.
Test using Postman
Postman is a useful tool for testing JSON endpoints. If you have never used Postman, you might consider completing a couple tutorials first.
Create a new request named AddObservation that exercises the Rest endpoint.
http://localhost:8080/observations/observation
Place one of the observations from the previously created JSON document in the request’s Body. Assign the type as JSON (application/json).
Click Send to send the request to the Spring Rest endpoint. If everything is correct, you should see the Observation as JSON printed to the command-line.
Copy the image base64 string and navigate to the CodeBeautify website’s Convert Your Base64 to Image webpage. Paste the string in the provided textarea and click Generate Image. If the base64 string was sent correctly, you should see the same image you sent to the Rest endpoint.
Create DynamoDB Client
Now that we have the basic Spring Boot application in place, we can start building the actual API to DynamoDB. But before working with DynamoDB, we need to create a DynamoDBClient instance.
Create a class named ObservationService in the com.tutorial.aws.dynamodb.service package.
Add the spring @Service annotation so spring sees this class as a controller.
Add the key and secretKey parameters and use the @Value annotation to indicate they are parameters from the application’s application.properties file (Spring Framework documentation).
Create a @PostConstruct and @PreDestroy methods (or implement a Spring InitializingBean).
Create a member variable entitled dynamoDbClient of type DynamoDbClient.
Instantiate and load the credentials for dynamoDbClient in the initialize method.
Close the dynamoDbClient in the preDestroy method.
The DynamoDBClient provides access to the DynamoDB API. All interaction with DynamoDB is done through this class. It has methods for for reading, writing, updating, and other interactions with DynamoDB tables and Items. For more information, refer to the API documentation.
Write Observation
Let’s first write an Observation to DynamoDB. Alternatively, you could say we PUT an item to DynamoB, as we are making an HTTP Put request to DynamoDB. We do this using the DynamoDBClient putItem method combined with a PutItemRequest.
Modify Service Class
Create a method named writeObservation that takes an Observation as a parameter.
Create a HashMap that uses String as the key and AttributeValue as the value.
Put each of the Observation variables into the HashMap, being sure the keys are correctly named. The keys should have exactly the same name as the JSON.
When creating the AttributeValueBuilder for each variable, ensure the correct datatype method is used.
Build a new PutItemRequest and then have dynamoDbClient call its putItem method to write the observation to the Observation DynamoDB table.
There are four different AttributeValue classes in the DynamoDB Java API. Here we use the one in the software.amazon.awssdk.services.dynamodb.model package (api documentation). Remember, tables store items. An item is comprised of one or more attributes. An AttributeValue holds the value for an attribute. AttributeValue has a builder (api documentation) used to build an AttributeValue instance. An attribute value can be a string, number, binary data, list, or collection. You use the appropriate method corresponding to the datatype to set the AttributeValue object’s value. For instance, for a String use s(String value), binary use b(SdkBytes b), and for a collection of strings use ss(Collection ss). For a complete list, refer to the API documentation.
AttributeValue instances are placed in a Map, where the key is the attribute’s name in the database table. The Observation’s attributes are mapped using the appropriate builder methods.
The tags are an optional list of strings, so we wrap it in a conditional and use,
if (observation.getTags() != null) {
observationMap.put("tags", AttributeValue.builder()
.ss(observation.getTags()).build());
}
PutItemRequest
The PutItemRequest wraps the JSON request sent to the DynamoDBClientputItem method. A PutItemRequestBuilder builds a PutItemRequest. Above, we first added the table name, followed by the item to put. The item is a key-value map of the observation’s attributes. After building the PutItemRequest instance, the DynamoDBClient instance uses the request to write the observation to the DynamoDBObservation table.
The GetItemRequest wraps a JSON Get request to DynamoDB. To fetch a particular Observation we must provide the id to the Get request. The key is a Map of AttributeValue items. In this case we added only one attribute, the id.
So far we have added and fetched an Observation to DynamoDB. Now let’s delete an Observation.
Modify Service Class
Add a deleteObservation method that takes an observation’s id as a parameter.
Create a HashMap to hold the attributes.
Build a new DeleteItemRequest and use the HashMap as the key.
Use the dynamoDbClient to delete the observation.
public void deleteObservation(String observationId) {
HashMap<String,AttributeValue> key = new HashMap<>();
key.put("id", AttributeValue.builder().s(observationId).build());
DeleteItemRequest deleteRequest = DeleteItemRequest.builder()
.key(key).tableName("Observation").build();
this.dynamoDbClient.deleteItem(deleteRequest);
}
DeleteItemRequest
The DeleteItemRequest wraps a JSON Delete HTTP request. As with all requests, we use a builder. The builder uses the table and the key to delete the Observation.
Create Rest Endpoint
Create a new Rest endpoint to delete observations.
Have the observation’s id passed to the endpoint as a path variable only add /delete after the variable.
Call the ObservationServicedeleteObservation method.
@DeleteMapping("/observation/{observationid}/delete")
public void deleteObservation(@PathVariable("observationid") String
observationId) {
this.observationService.deleteObservation(observationId);
}
Test with Postman
Create a new Request using Postman.
Assign it DELETE from the dropdown to indicate it is an Http Delete request.
Click Send and the record should be deleted. Navigate to the Items in the AWS Console to ensure the Observation was deleted.
Update Observation
An Observation can have one or more tags. This is something that seems likely to be added at a later date and/or modified. Let’s create an endpoint that allows adding/modifying an observation’s tags.
Update Service Class
Create a method named updateObservationTags that takes a list of tags and an observation id as parameters.
Create a HashMap to hold AttributeValue objects.
Use the AttributeBuilderValue builder to add the tags to the HashMap with :tagval as the key.
Create a second HashMap to hold the observation’s id.
Build an UpdateItemRequest that uses an update expression.
The DynamoDBClient instance uses the UpdateItemRequest to build the request to update the item. As with fetching and deleting, it needs a key to properly select the correct item. But it also needs the values to update. You provide an update expression and then provide the attributes. Note that the key for the attribute, :tagval, matches the expression. The request then uses the key and the update expression to update the item.
Add Rest Endpoint
Add an endpoint that takes the observation id as a path variable and a JSON array of tags as the request body.
Call the ObservationServiceupdateObservationTags method.
The DynamoDbClientbatchWriteItem method takes a BatchWriteItemRequest as a parameter. The BatchWriteItem can write or delete up to 25 items at once and is limited to 16 MB of data. Note that it still makes as many calls as you have items; however, it makes these calls in parallel.
You create a List to hold the WriteRequest for each Observation. Each Observation is written to a Map as key-value pairs. The map is added to a WriteRequest, which is then added to the list until all observations are prepared as WriteRequest instances.
Each list of WriteRequest instances is added to another map. The table name is the key and the list is the values. In this way a single batch write could write to multiple tables. After creating the map of the lists of WriteRequest instances, the whole thing is used to create a BatchWriteItemRequest which is used by the DynamoDbClientbatchWriteItem method.
Click Send then navigate to the AWS Console Observation table’s items and the observations should be added.
Conditionally Fetch Observations
A common requirement is to fetch records based upon certain criteria. For example, suppose we wish to fetch all observations belonging to a particular station. When using DynamoDB any variable used for a query must be indexed. So before creating a query, we first create an index on the Observation table’s stationid variable.
Create Index
Navigate to the Observation table in the AWS Console.
Click Create Index.
Select stationid as the index’s partition key and be certain to define it as a Number.
Click Create Index to create the index.
Secondary Indexes
Secondary Indexes allow retrieving data from a table using an attribute other than the primary key. You retrieve data from the index rather than the table. For more on DynamoDB secondary indexes, refer to the following article by LinuxAcademy: A Quick Guide to DynamoDB Secondary Indexes.
We then added the Condition to a map and specified stationid as the key and condition as the value. We then built the QueryRequest using its associated builder.
There are several topics not explored in this tutorial. First, you can scan a database table. When you scan the table you return all the items in the table. Second, this tutorial did not discuss conditionally updating or deleting items. However, the principles are the same as conditionally querying a table for items. Also, it is helpful to explore the command-line examples for working with DynamoDB, as they help understand the SDK. Finally, we did not cover the Java 1.1 AWS SDK.
From Java 1.1 AWS SDK to Java 2 AWS SDK
There are many more examples and tutorial on the Web using the Java 1.1 API rather than the Java 2 API. However, the primary difference between the two versions is the builder pattern. Many, if not most, of the Java 1.1 tutorials remain useful. The pattern is the same:
create a request type
setup the request with the desired parameters,
pass the request to the DynamoDB client,
obtain the result.
In the Java 1.1 SDK you perform these steps using constructors and setters and getters. In the Java 2 SDK you use builders. Practically all classes in the Java 2 AWS SDK use builders. Use this as a starting point if you have a particularly good tutorial using the Java 1.1. SDK. Although not foolproof, doing this has helped me translate many Java 1.1. examples to Java 2 SDK.
This tutorial, although it uses the Java 1 AWS API, is a very good introduction covering the same topics in this tutorial. Just remember, think builder, although the techniques in the API are the same, the Java 2 version of the API uses builders extensively.
Conclusion
In this tutorial we explored the lower-level API of the Java 2 SDK by using the AWS DynamoDB Java API. We wrote an item, updated an item, deleted an item, and batch uploaded items. We also explored conditionally querying items.
As with all of the SDK, it is based upon builders, requests, and the client. You build a request to pass to the DynamoDBClient which in turn returns a response. You do not create a new instance of a request and set properties via setters, but rather, you use a builder to build a request.
DynamoDB is a non-relational database and so you cannot just write a conditional query on any field. You can only use fields that are indexed in a query. This seems logical if you consider that DynamoDB is designed for massive amounts of data that is relatively unstructured.
This tutorial might leave you wanting more. Rather than giving you explicitif this then do that advice, I show you three different techniques you might use for handling Spring Boot 2 REST Exceptions. Those of you with experience might ask why even bother, as Spring Boot handles exceptions and presents a nice REST response by default. However, there are instances where you might require customizing exception handling, and this tutorial demonstrates three techniques. As with the other tutorials on this site, the caveat emptor applies…if you follow this tutorial with a different version of Spring Boot, or worse, Spring without the Boot, then be prepared to do further research, as Spring Boot 2’s primary purpose is to simplify Spring development. With simplification, many of the implementation details become hidden.
There are three ways we can handle exceptions using Spring Boot 2 Rest Exceptions: the default handling, exception handling in the controller, or global exception handling. In this tutorial we explore all three ways of handling exceptions.
Project Setup
Before beginning, create your Spring Boot application. If you are new to Spring Boot then you should refer to one of the tutorials here, or on the web before attempting this tutorial. This tutorial assumes you can create, compile, and run a Spring Boot REST application. It also assumes you know how to call a REST endpoint.
The response is not very helpful when an incorrect value for type is passed to the rest endpoint. Moreover, the response will likely result in a client application throwing a NullPointerException, as both greeting and goodbye are null. Instead, we should throw an exception when an incorrect value is passed to the endpoint.
As an aside, yes, HelloGoodbye is poorly designed. Returning a null is bad programming practice. A better option would be to do something as follows. But, creating well-designed pojos is not this tutorial’s intention. Instead, go with the poorly designed HelloGoodbye implementation above.
public class HelloGoodbye {
private String message;
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String msg) {
this.message = msg;
}
}
Default Exception Handling
Spring Boot provides exception handling by default. This makes it much easier for both the service endpoint and client to communicate failures without complex coding.
Modify createGreeting to throw an Exception if type is not the value hello or goodbye.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public HelloGoodbye createGreeting(String type) throws Exception {
HelloGoodbye helloGoodbye = new HelloGoodbye();
if (type.equals("hello")) {
helloGoodbye.setGreeting("Hello there.");
} else if (type.equals("goodbye")) {
helloGoodbye.setGoodbye("Goodbye for now.");
} else {
throw new Exception("Valid types are hello or goodbye.");
}
helloGoodbye.setType(type);
return helloGoodbye;
}
}
Modify GreetingControllergetGreeting to throw an Exception.
Compile, run the application, and visit the rest endpoint. Note the response returns the error as json.
{
"timestamp": "2019-04-06T18:07:34.344+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Valid types are hello or goodbye.",
"path": "/greeting/greet"
}
When changing the createGreeting method we were required to either catch the exception or throw it. This is because Exception is a checked exception (more on checked exceptions). But there were no special requirements for returning that exception to a client application as JSON. This is because Spring Boot provides a default JSON error message for errors. The relevant class is DefaultErrorAttributes which implements the ErrorAttributes interface. This class provides the following attributes when an exception occurs: timestamp, status, error, exception, message, errors, trace, and path. You can easily override the default with your own error attributes class; however, this technique is not illustrated here. Refer to this tutorial for more information on writing a custom implementation of the ErrorAttributes interface (Customize error JSON response with ErrorAttributes).
Usually, business logic exceptions warrant a business logic exception rather than a generic exception. Let’s modify the code to throw a custom exception.
Create a class named GreetingTypeException that extends Exception.
Assign it an bad request status through the @ResponseStatus annotation.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class GreetingTypeException extends Exception {
private static final long serialVersionUID = -189365452227508599L;
public GreetingTypeException(String message) {
super(message);
}
public GreetingTypeException(Throwable cause) {
super(cause);
}
public GreetingTypeException(String message, Throwable cause)
{
super(message, cause);
}
}
Modify createGreeting to throw a GreetingTypeException rather than an Exception.
public HelloGoodbye createGreeting(String type) throws GreetingTypeException {
HelloGoodbye helloGoodbye = new HelloGoodbye();
if (type.equals("hello")) {
helloGoodbye.setGreeting("Hello there.");
} else if (type.equals("goodbye")) {
helloGoodbye.setGoodbye("Goodbye for now.");
} else {
throw new GreetingTypeException("Valid types are hello or goodbye.");
}
helloGoodbye.setType(type);
return helloGoodbye;
}
Compile, run the application, and visit the rest endpoint. Assign an incorrect value to the type parameter.
http://localhost:8080/greeting/greet?type=cc
{
"timestamp": "2019-03-29T01:54:40.114+0000",
"status": 400,
"error": "Bad Request",
"message": "Valid types are hello or goodbye.",
"path": "/greeting/greet"
}
Create an exception named NameNotFoundException. Have the exception extend RuntimeException rather than Exception.
Assign it a response status of not found.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NameNotFoundException extends RuntimeException {
public NameNotFoundException(String message) {
super("The id: " + message + " could not be found.");
}
}
Modify GreetingServicecreateGreeting method to take id as an integer.
Create a new method called getPersonName. Suspend disbelief and implement it as below. Obviously in a real-world project you would get user information from a database, LDAP server, or some other datastore.
Modify createGreeting to use the getPersonName method to personalize the greeting.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public HelloGoodbye createGreeting(String type, int id) throws GreetingTypeException {
HelloGoodbye helloGoodbye = new HelloGoodbye();
if (type.equals("hello")) {
helloGoodbye.setGreeting("Hello there " +
this.getPersonName(id));
} else if (type.equals("goodbye")) {
helloGoodbye.setGoodbye("Goodbye for now " +
this.getPersonName(id));
} else {
throw new GreetingTypeException("Valid types are hello or goodbye.");
}
helloGoodbye.setType(type);
return helloGoodbye;
}
public String getPersonName(int id) {
if(id==1) {
return "Tom";
} else if(id==2) {
return "Sue";
} else {
throw new NameNotFoundException(Integer.toString(id));
}
}
}
Modify GreetingController to take id as a request parameter and modify its call to the GreetingServicecreateGreeting method to also pass id to the service.
{
"timestamp": "2019-03-31T20:30:18.727+0000",
"status": 404,
"error": "Not Found",
"message": "The id: 6 could not be found.",
"path": "/greeting/greet"
}
As an aside, notice that we had NameNotFoundException extend RuntimeException and not Exception. By doing this we made NameNotFoundException an unchecked exception (more on unchecked exceptions) and were not required to handle the exception.
Controller Error Handlers
Although Spring Boot’s default exception handling is robust, there are times an application might require more customized error handling. One technique is to declare an exception handling method in a rest controller. This is accomplished using Spring’s @Exceptionhandler annotation (javadoc).
Create a new simple class named GreetingError. Note that it is a pojo and does not extend Exception.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import java.util.Date;
public class GreetingError {
private Date timestamp;
private String message;
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Modify GreetingController to have a method named nameNotFoundException that is annotated with an @ExceptionHandler annotation.
Implement nameNotFoundException to return a ResponseEntity<>.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
@RestController
@RequestMapping(value = "/greeting")
public class GreetingController {
@Autowired
protected GreetingService service;
@GetMapping("/greet")
public HelloGoodbye getGreeting(@RequestParam("type") String type, @RequestParam("id") int id) throws Exception {
HelloGoodbye goodBye = service.createGreeting(type, id);
return goodBye;
}
@ExceptionHandler(NameNotFoundException.class)
public ResponseEntity<?> nameNotFoundException(NameNotFoundException ex, WebRequest request) {
GreetingError errorDetails = new GreetingError();
errorDetails.setTimestamp(new Date());
errorDetails.setMessage("This is an overriding of the standard exception: " + ex.getMessage());
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
}
Compile, run the application, and visit the endpoint.
{
"timestamp": "2019-04-01T02:14:51.744+0000",
"message": "This is an overriding of the standard exception: The id: 33 could not be found."
}
The default error handling for NameNotFoundException is overridden in the controller. But you are not limited to implementing one error handler in a controller, you can define multiple error handlers, as in the code below.
Modify GreetingController to throw an arithmetic exception in getGreeting.
Create a new exception handler for ArithmeticException.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
@RestController
@RequestMapping(value = "/greeting")
public class GreetingController {
@Autowired
protected GreetingService service;
@GetMapping("/greet")
public HelloGoodbye getGreeting(@RequestParam("type") String type, @RequestParam("id") int id) throws Exception {
int i = 0;
int k = 22/i;
HelloGoodbye goodBye = service.createGreeting(type, id);
return goodBye;
}
@ExceptionHandler(NameNotFoundException.class)
public ResponseEntity<?> nameNotFoundException(NameNotFoundException ex, WebRequest request) {
GreetingError errorDetails = new GreetingError();
errorDetails.setTimestamp(new Date());
errorDetails.setMessage("This is an overriding of the standard exception: " + ex.getMessage());
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<?> arithmeticException(ArithmeticException ex, WebRequest request) {
GreetingError errorDetails = new GreetingError();
errorDetails.setTimestamp(new Date());
errorDetails.setMessage("This is an overriding of the standard exception: " + ex.getMessage());
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Compile, run the application, and visit the rest endpoint.
{
"timestamp": "2019-04-01T02:40:53.527+0000",
"message": "This is an overriding of the standard exception: / by zero"
}
Before continuing, do not forget to remove the code that divides by zero.
The Exception handler is a useful annotation that allows handling exceptions within a class. We used it in our controller to handle exceptions. The method used to handle the exception returned a ResponseEntity<T> class (javadoc). This class is a subclass of HttpEntity (javadoc). The HttpEntity wraps the actual request or response – here the response – while the ResponseEntity adds the HttpStatus code. This allows you to return a custom response from your rest endpoint.
Global Error Handler
The @ControllerAdvice is a way to handle exceptions within Spring Controllers. It allows using a method annotated with the @ExceptionHandler to handle all exceptions in an application.
Create a new class named GreetingExceptionHandler.
Annotate it with the @ControllerAdvice annotation.
Copy and paste the nameNotFoundException method from the GreetingController class. Change the message text to be certain it is, in fact, being called.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import java.util.Date;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GreetingExceptionHandler {
@ExceptionHandler(NameNotFoundException.class)
public ResponseEntity<?> nameNotFoundException(NameNotFoundException ex, WebRequest request) {
GreetingError errorDetails = new GreetingError();
errorDetails.setTimestamp(new Date());
errorDetails.setMessage("This a global exception handler: " + ex.getMessage());
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
}
Remove the NameNotFoundException exception handler from the GreetingController class.
package com.tutorial.exceptions.spring.rest.exceptionstutorial;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
@RestController
@RequestMapping(value = "/greeting")
public class GreetingController {
@Autowired
protected GreetingService service;
@GetMapping("/greet")
public HelloGoodbye getGreeting(@RequestParam("type") String type, @RequestParam("id") int id) throws Exception {
HelloGoodbye goodBye = service.createGreeting(type, id);
return goodBye;
}
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<?> arithmeticException(ArithmeticException ex, WebRequest request) {
GreetingError errorDetails = new GreetingError();
errorDetails.setTimestamp(new Date());
errorDetails.setMessage("This is an overriding of the standard exception: " + ex.getMessage());
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Compile, run the application, and visit the rest endpoint. You receive the error created in the global handler.
{
“timestamp”: “2019-04-06T21:21:17.258+0000”,
“message”: “This a global exception handler: The id: 33 could not be found.”
}
The @ControllerAdvice annotation (Javadoc) allows an exception handler to be shared across controllers. It is useful if you wish creating uniform exception handling across multiple controllers. You can limit the @ControllerAdvice exception handling to apply only to certain controllers, for more information, refer to the Javadoc.
Conclusion
Spring Boot 2 Rest Exceptions and handling them is both easy and difficult. It is easy because there are concrete ways to implement exception handling. Moreover, even if you provide no exception handling, it is provided for you by default. It is difficult because there are many different ways to implement exception handling. Spring provides so much customization, so many different techniques, it is sometimes easy to become lost in the details.
In this tutorial we explored three different techniques when dealing with Spring Boot 2 REST Exceptions. You should refer to other tutorials before deciding any one technique is what you should use. In the interest of full disclosure, I personally feel the @ControllerAdvice technique is the most robust, as it allows creating a unified exception handling framework.
In the next few posts I will be exploring implementing microservices using REST, messaging, and finally Amazon Web Services (AWS) Lambda. Although the tutorials are largely written in a step-by-step format, we also explore the underlying theory of microservice architecture. In this tutorial we explore REST using Spring Boot.
In this first tutorial, I assume Eclipse, Maven, and Postman. If new to Java then you are strongly recommended to begin by first going through this book, Think Java, along with my accompanying tutorials. There are also many links to excellent YouTube tutorials that accompany the step-by-step tutorials provided. If you are new to Maven and/or Eclipse, then here are two introductory tutorials on Maven and Eclipse.
Let’s begin by building a simple Hello World REST endpoint using Spring Boot. Spring boot is an easy way to create Spring applications without requiring web server installation, Spring configuration files, and other necessities of standing a Spring application. You can quickly create and run a Spring application. Although useful for tutorials, and it is used in production, if you do not understand more traditional Spring applications, you should also learn the details of more traditional Spring application configuration before going to a job interview. But in this and the next several tutorials, I assume Spring Boot.
Create a new Maven project named rest-tutorial. If you have never created a Maven application in Eclipse, refer to this tutorial.
Create the following two packages: com.tutorial.spring.application and com.tutorial.spring.rest in the rest-tutorialproject. (how to create a package)
Create the class Hello.java in the com.tutorial.spring.rest package. (how to create a class)
package com.tutorial.spring.rest;
public class Hello {
private String greeting;
public String getGreeting() {
return greeting;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
}
Create a class with a main method named TutorialApplication in the com.tutorial.spring.application package.
Add the @SpringBootApplication and @ComponentScan annotations to the class.
package com.tutorial.spring.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.tutorial.spring.rest" })
public class TutorialApplication {
public static void main(String[] args) {
SpringApplication.run(TutorialApplication.class, args);
}
}
An annotation is metadata that provides more information to the compiler and that can be interpreted at runtime (introduction to annotations). Spring Boot uses the @SpringBootApplication annotation to signify that a class is the starting point for a Spring Boot application. It also provides default values for the @Configuration, @EnableAutoConfiguration, and @ComponentScan Spring annotations. However, notice we also use the @ComponentScan annotation because we do not wish using the provided default value. Instead, we explicitly instruct Spring to scan for Spring classes in the com.tutorial.spring.rest package.
Create another class named HelloController in the com.tutorial.spring.application.rest package.
Add the @RestController and @RequestMapping annotations to the class.
package com.tutorial.spring.rest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
@RestController
@RequestMapping(value="/hello")
public class HelloController {
@RequestMapping(value = "/greeting", method = RequestMethod.GET)
public Hello greeting() {
Hello hello = new Hello();
hello.setGreeting("Hello there.");
return hello;
}
}
The @RequestController annotation serves two purposes, it defines the class as a Spring controller and as a REST endpoint (more on @RequestController and @Controller). The first @RequestMappinguse defines the hello endpoint or http://localhost:8080/hello. The second defines the greeting endpoint and is used with the previous endpoint to form http://localhost/hello/greeting. It defines the HTTP method as GET (more on GET and POST).
Build and run the application in Eclipse. You should see log messages from the Spring Boot embedded web server in the Eclipse Console. Note the line that confirms the greeting endpoint was mapped to the method developed.
019-02-24 16:11:52.675 INFO 3491 --- [ main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/hello/greeting],methods=[GET]}" onto public com.tutorial.spring.rest.Hello
com.tutorial.spring.rest.HelloController.greeting()
Open a web browser and type http:localhost:8080/hello/greeting in as the address to navigate to the endpoint. You should see the following.
But a web browser is intended for results viable for consumption by an end-user. For instance, an HTML page renders in a web browser and is visible to the viewer. A Rest service, in contrast, is intended to be used by another application. Moreover, the endpoint’s purpose is to provide data and not display the data. Or,
REST, or REpresentational State Transfer, is an architectural style for providing standards between computer systems on the web, making it easier for systems to communicate with each other. REST-compliant systems, often called RESTful systems, are characterized by how they are stateless and separate the concerns of client and server. We will go into what these terms mean and why they are beneficial characteristics for services on the Web (code academy).
Moreover, note the displayed results. The results are displayed using JavaScript Object Notation (JSON). This notation is for easy communication between programs rather than human end-user consumption (more on JSON).
Instead of using a browser, let’s use a free tool named Postman. It is designed specifically for Rest developers. Download and install Postman if you do not already have it installed. You can obtain it here. For more information on Postman refer to the many tutorials provided.
Start Postman and create a new Collection named SprintMicroservicesTutorial.
Create a new Request named Greeting. Ensure the method is GET and has the following URL http://localhost:8080/greeting (creating a GET request).
Click Send and the following should appear as the response.
{
"greeting": "Hello there."
}
Congratulations, you created your first Rest web service.
API Design
REST is designed like webpages, except with computers as the consumers. Just as you would almost never navigate to a page conceptually represented by a verb you should never represent a REST endpoint by such. Use nouns. Endpoints, like webpages, are resources.
REST endpoints are also designed to be layered into hierarchies, the same as webpages. Suspend disbelief and let’s assume we have a simple endpoint to a dogfood and catfood database. Obviously, the example is greatly simplified.
The endpoint is food. The API provides information on food for two species: dogs and cats. Each species has thousands of possible breeds. And each food has three possible sizes: small, medium, and large.
For purposes of illustration, we chose to make dog and cat two separate endpoints. And because there are thousands of potential breeds, we make them what are called path parameters. Finally, we choose a query parameter to represent the food size.
Path and Query Parameters
Parameters that are part of an endpoint path are termed path parameters. They are distinguished by curly braces. For example,
http://www.president.com/{president}
represents an endpoint where the client replaces the president with the name of the specific president.
A query string parameter are represented after the endpoint by using a question mark to offset the query string.
http://ww.president.com/{president}?age=<age>
Create a new com.tutorial.spring.rest.petfood package. Create three new classes named Food, DogFood, and CatFood. Create enumerations for the species and size properties.
package com.tutorial.spring.rest.petfood;
public class Food {
public enum Species {DOG, CAT}
public enum Size {SMALL, MEDIUM, LARGE}
private String brand;
private Double price;
private Size size;
private Species species;
private String enteredBreed;
public String getEnteredBreed() {
return enteredBreed;
}
public void setEnteredBreed(String enteredBreed) {
this.enteredBreed = enteredBreed;
}
public Species getSpecies() {
return species;
}
public void setSpecies(Species species) {
this.species = species;
}
public Size getSize() {
return size;
}
public void setSize(Size size) {
this.size = size;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
package com.tutorial.spring.rest.petfood;
public class DogFood extends Food {
}
package com.tutorial.spring.rest.petfood;
public class CatFood extends Food {
}
Create a class named FoodController. Provide it with a @RestController annotation and a top-level @RequestMapping for the /food endpoint.
Create two endpoint methods, one for the /dog endpoint and one for the /cat endpoint.
The {breed} combined with the @PathVariable annotation is how you define a path parameter. The @RequestParam annotation is how you define a parameter bound to a query parameter.
Create a new class named FoodService and add simple methods to create a list containing cat food and a list containing dog food.
Annotate the class with the @Service annotation.
package com.tutorial.spring.rest.petfood;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.tutorial.spring.rest.petfood.Food.Size;
import com.tutorial.spring.rest.petfood.Food.Species;
@Service
public class FoodService {
public List<CatFood> createCatFood(String breed, Size size)
{
List<CatFood> food = new ArrayList<CatFood>();
CatFood a = new CatFood();
a.setBrand("Purina");
a.setPrice(13.12);
a.setSize(size);
a.setSpecies(Species.CAT);
a.setEnteredBreed(breed);
food.add(a);
CatFood b = new CatFood();
b.setBrand("Science Diet");
b.setPrice(10.00);
b.setSize(size);
b.setSpecies(Species.CAT);
b.setEnteredBreed(breed);
food.add(b);
return food;
}
public List<DogFood> createDogFood(String breed, Size size)
{
List<DogFood> food = new ArrayList<DogFood>();
DogFood a = new DogFood();
a.setBrand("Purina");
a.setPrice(33.22);
a.setSize(size);
a.setSpecies(Species.DOG);
a.setEnteredBreed(breed);
food.add(a);
DogFood b = new DogFood();
b.setBrand("Science Diet");
b.setPrice(12.22);
b.setSize(size);
b.setSpecies(Species.DOG);
b.setEnteredBreed(breed);
food.add(b);
return food;
}
}
The annotation causes the class to be scanned by Spring when performing classpath scanning. Spring registers the class as a service. An instance of this class is then available to other classes without explicit instantiation using a constructor. You then auto-wire it to other classes using the @Autowired annotation.
The @Autowired annotation marks an object as automatically injected using Spring’s dependency injection. An instance of the FoodService class is annotated with @Service and so is loaded by Spring and subsequently injected into the FoodController instance. The FoodController instance can then use the Foodservice instance as if it had explicitly created it using a constructor.
Create two new GET requests in Postman. One for cat food and one for dog food.
For the cat endpoint enter tiger as breed and LARGE as size.
For the dog endpoint enter chihuahua as breed and SMALL as size.
What is returned is an array of cat foods and a list of dog foods (JSON arrays).
In this tutorial we explored REST using Spring Boot. We used Spring Boot to create several Rest endpoints. You learned a little of the reasoning behind the idea of a Restful API.