Jersey is the reference implementation of the Jakarta RESTful Web Services (formerly JAX‑RS) specification. Jersey 3.x targets Jakarta EE 9+, which completed the rename of all javax.* packages to jakarta.*. This is a breaking change from Jersey 2.x – any code using javax.ws.rs must be updated to jakarta.ws.rs. This guide builds a working REST API from scratch using Jersey 3 with the JDK's built‑in HTTP server, requiring no application server.


The javaxjakarta Migration

Jakarta EE 9 (released 2020) renamed all javax.* packages to jakarta.*. The key imports that change for REST development are:

Jakarta EE 8 / Jersey 2 Jakarta EE 9+ / Jersey 3
javax.ws.rs.* jakarta.ws.rs.*
javax.ws.rs.core.* jakarta.ws.rs.core.*
javax.ws.rs.ext.* jakarta.ws.rs.ext.*
javax.inject.* jakarta.inject.*
javax.json.* jakarta.json.*

If you are migrating an existing Jersey 2 application, you'll need to:

  • Update all imports from javax.ws.rs to jakarta.ws.rs.
  • Update your Maven/Gradle dependencies to Jersey 3.x.
  • Update your application server to Jakarta EE 9+ if you deploy to one.

Maven Dependencies

Here's the complete pom.xml dependency set for Jersey 3 with the JDK HTTP container and Jackson JSON support:

<properties>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  <jersey.version>3.1.5</jersey.version>
</properties>

<dependencies>
  <!-- Jersey core server -->
  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
    <version>${jersey.version}</version>
  </dependency>

  <!-- JDK HTTP server container -->
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-jdk-http</artifactId>
    <version>${jersey.version}</version>
  </dependency>

  <!-- Dependency injection support -->
  <dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>${jersey.version}</version>
  </dependency>

  <!-- JSON support via Jackson -->
  <dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey.version}</version>
  </dependency>
</dependencies>

Note: The jersey-hk2 dependency provides HK2 (a lightweight dependency injection framework) that Jersey uses for internal wiring. You may also use CDI if you're in a Jakarta EE container, but for a standalone app, HK2 is the default.


Application Class

The Application subclass is the entry point for JAX‑RS configuration. It tells Jersey which resource classes and providers to register:

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import java.util.Set;

@ApplicationPath("/api")
public class MyApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        return Set.of(
            MenuResource.class
        );
    }
}

The @ApplicationPath annotation sets the base URI path for all resources. All endpoints defined in MenuResource will be available under /api/.... You can also use ResourceConfig (a Jersey subclass) for programmatic registration, which we'll see later.


Resource Class

The resource class defines the REST endpoints. Notice the use of jakarta.ws.rs.* annotations:

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Collection;

@Path("/menu")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MenuResource {

    private final MenuService service = new MenuService();

    @GET
    public Collection<MenuItem> getAll() {
        return service.findAll();
    }

    @GET
    @Path("/{id}")
    public Response getById(@PathParam("id") int id) {
        MenuItem item = service.findById(id);
        if (item == null) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }
        return Response.ok(item).build();
    }

    @POST
    public Response create(MenuItem item) {
        MenuItem created = service.save(item);
        return Response.status(Response.Status.CREATED).entity(created).build();
    }

    @PUT
    @Path("/{id}")
    public Response update(@PathParam("id") int id, MenuItem item) {
        item.setId(id);
        MenuItem updated = service.update(item);
        return Response.ok(updated).build();
    }

    @DELETE
    @Path("/{id}")
    public Response delete(@PathParam("id") int id) {
        service.delete(id);
        return Response.noContent().build();
    }
}

Notice the use of Response for fine‑grained control over HTTP status codes and headers. The @Produces and @Consumes at the class level apply to all methods unless overridden.


Model and Service Classes

MenuItem.java

public class MenuItem {
    private int id;
    private String name;
    private double price;

    public MenuItem() {}

    public MenuItem(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    // getters and setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

MenuService.java – In‑memory store

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

public class MenuService {
    private final Map<Integer, MenuItem> store = new HashMap<>();
    private final AtomicInteger idGen = new AtomicInteger(1);

    public MenuService() {
        save(new MenuItem(0, "Burger", 8.99));
        save(new MenuItem(0, "Pizza",  11.49));
        save(new MenuItem(0, "Salad",  6.99));
    }

    public Collection<MenuItem> findAll() { return store.values(); }

    public MenuItem findById(int id) { return store.get(id); }

    public MenuItem save(MenuItem item) {
        int id = idGen.getAndIncrement();
        item.setId(id);
        store.put(id, item);
        return item;
    }

    public MenuItem update(MenuItem item) {
        store.put(item.getId(), item);
        return item;
    }

    public void delete(int id) { store.remove(id); }
}

The service is simple and not thread‑safe – for a real application, you'd use a proper database, but this serves the purpose of the example.


Main Class: Starting the Server

The main class uses JdkHttpServerFactory to create an HTTP server that delegates requests to Jersey:

import com.sun.net.httpserver.HttpServer;
import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.net.URI;

public class Main {

    private static final URI BASE_URI = URI.create("http://localhost:8080/");

    public static void main(String[] args) throws Exception {
        ResourceConfig config = new ResourceConfig()
            .register(MenuResource.class)
            .packages("com.example"); // or list resource classes explicitly

        HttpServer server = JdkHttpServerFactory.createHttpServer(BASE_URI, config);
        System.out.println("Server started at " + BASE_URI);
        System.out.println("API available at " + BASE_URI + "api/menu");
        System.out.println("Press Ctrl+C to stop.");

        // Keep running until interrupted
        Thread.currentThread().join();
    }
}

ResourceConfig is a Jersey‑specific subclass of Application that allows programmatic registration of resources and features. The packages() method scans the given package for JAX‑RS annotated classes, which is convenient when you have many resources.


Building and Running

Add the exec plugin to your pom.xml to run directly from Maven:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.1.0</version>
  <configuration>
    <mainClass>com.example.Main</mainClass>
  </configuration>
</plugin>

Then build and run:

mvn clean compile exec:java

Test the endpoints using curl:

# Get all menu items
curl http://localhost:8080/api/menu

# Get a specific item
curl http://localhost:8080/api/menu/1

# Create a new item
curl -X POST http://localhost:8080/api/menu \
  -H "Content-Type: application/json" \
  -d '{"name":"Pasta","price":9.99}'

# Update an item
curl -X PUT http://localhost:8080/api/menu/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Cheeseburger","price":10.99}'

# Delete an item
curl -X DELETE http://localhost:8080/api/menu/1

Exception Handling with ExceptionMapper

To handle exceptions globally and return meaningful HTTP responses, implement an ExceptionMapper. For example, handling NotFoundException:

import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

@Provider
public class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {

    @Override
    public Response toResponse(NotFoundException exception) {
        return Response.status(Response.Status.NOT_FOUND)
                       .entity("Resource not found")
                       .build();
    }
}

Register it in your ResourceConfig:

ResourceConfig config = new ResourceConfig()
    .register(MenuResource.class)
    .register(NotFoundExceptionMapper.class)
    .packages("com.example");

Now, if any resource method throws NotFoundException, the mapper will produce a 404 response with the custom message.


Customising JSON with Jackson

Jersey’s Jackson integration allows you to use annotations to control serialization. For example, you can:

  • Rename a field using @JsonProperty.
  • Ignore fields with @JsonIgnore.
  • Use @JsonFormat for date formatting.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;

public class MenuItem {
    @JsonIgnore
    private int internalId; // not serialized

    @JsonProperty("id")
    private int id;

    @JsonProperty("itemName")
    private String name;

    // ... getters/setters
}

The Jackson provider is automatically enabled when you include jersey-media-json-jackson in your dependencies.


Deployment Options

The JDK HTTP server approach is great for microservices, embedded applications, and demos. However, for production, you might consider:

  • Servlet containers – deploy your application as a WAR file to Tomcat, Jetty, or any Servlet 5+ container (Jakarta EE 9+). Use the jersey-container-servlet dependency.
  • Spring Boot – Spring Boot 3 uses Jakarta EE 9 and has built‑in support for Jersey (you can replace Spring MVC with Jersey).
  • Helidon SE – a microservices framework that also uses Jakarta REST and can be run with the JDK HTTP server.

For a simple, self‑contained application, the JDK HTTP server is a lightweight and sufficient choice.


Jersey 2 vs Jersey 3 Summary

Aspect Jersey 2 Jersey 3
Namespace javax.ws.rs jakarta.ws.rs
Spec version JAX‑RS 2.x Jakarta REST 3.x
Jakarta EE version EE 8 and earlier EE 9+
Java version required Java 8+ Java 11+
HK2 DI version 2.x 3.x

Summary

Jersey 3 implements the Jakarta REST 3 specification with the jakarta.ws.rs package namespace (replacing javax.ws.rs from Jersey 2). To build a REST API:

  • Add the Jersey 3 dependencies (core, JDK container, HK2, and Jackson).
  • Create an Application subclass (or ResourceConfig) to configure the app.
  • Annotate resource classes with @Path, @GET/@POST/etc.
  • Start the server using the JDK HTTP container with JdkHttpServerFactory.

The jersey-hk2 dependency provides dependency injection, and jersey-media-json-jackson handles JSON serialization automatically. For production, consider deploying to a Servlet container or using a framework like Spring Boot, but the JDK HTTP server is perfect for quick, standalone services.


Happy coding!

Every developer has hit a badly designed API — cryptic endpoint names, inconsistent responses, no clear pattern for anything. REST (Representational State Transfer) was invented specifically to avoid this mess. Proposed by Roy Fielding in his 2000 doctoral dissertation, REST is an architectural style built on a small set of constraints. Follow them and you get an API that's predictable, scalable, and enjoyable to use. Ignore them and you get... the other kind.

In this article, we’ll break down the five core constraints of REST, explore the Richardson Maturity Model, and cover practical best practices that will make your API a joy to consume.


1. Uniform Interface — Consistency Is King

This is the most important constraint and the one that gives REST APIs their recognizable shape. The idea is simple: every resource in your system should be accessible through a consistent, predictable interface.

In practice, this means a few things:

  • Resources are identified by URIs. A product is /api/products/42, not /api/getProductById?id=42.
  • Use nouns, not verbs. The HTTP method (GET, POST, PUT, DELETE) already expresses the action — the URI should only describe the resource.
  • Use plural nouns. /products not /product; /users not /user.
// Bad — mixing verbs into the URL
GET  /getProducts
POST /createProduct
PUT  /updateProduct/42
GET  /deleteProduct/42

// Good — HTTP verbs do the work, URIs name the resource
GET    /api/products       // list all products
POST   /api/products       // create a new product
GET    /api/products/42    // get product 42
PUT    /api/products/42    // update product 42
DELETE /api/products/42    // delete product 42

When your API follows this pattern, any developer can look at a URI and immediately understand what resource it refers to, and look at the HTTP method to understand what operation is being performed.


2. Client-Server — Keep Your Concerns Separated

The client and server should be completely independent of each other. The client handles the user interface; the server handles data storage and business logic. They communicate only through the API — neither knows how the other is implemented.

Why does this matter? Because it lets both sides evolve independently:

  • You can rewrite your mobile app in a completely new framework without touching the server.
  • You can migrate your database from MySQL to PostgreSQL without the clients noticing.
  • You can scale the server horizontally without any changes to client code.
Real‑world benefit: A team can work on the React frontend and the Java backend at the same time, as long as they agree on the API contract (request/response format). Neither team blocks the other.

3. Stateless — Each Request Stands on Its Own

Every request from a client to the server must contain all the information needed to process it. The server doesn't store any session state between requests — it processes each request in complete isolation.

// Stateful — server needs to remember who you are
GET /api/my-orders          // server looks up session to know "who is 'me'"

// Stateless — client sends all required context
GET /api/orders?userId=123
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...   // JWT carries identity

Statelessness is what makes REST APIs so easy to scale. If the server holds no session state, any server in a load‑balanced cluster can handle any request. There's no need for sticky sessions or shared session caches. Each request is an atomic, self‑contained operation.

It also makes your API far easier to debug — if a request fails, you don't need to reconstruct a session history to reproduce the problem. Just replay the exact same HTTP request.


4. Cacheable — Be Explicit About What Can Be Cached

HTTP has a powerful built‑in caching system. REST APIs should take advantage of it by explicitly marking responses as cacheable or non‑cacheable. A cached response can be reused for subsequent equivalent requests — reducing server load and improving response times for clients.

// Response that can safely be cached for 5 minutes
HTTP/1.1 200 OK
Cache-Control: max-age=300, public
ETag: "abc123"
Content-Type: application/json

{"id": 42, "name": "Widget", "price": 9.99}

// Response that must never be cached (e.g., user‑specific or real‑time data)
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: application/json

A product catalog that changes once a day? Cache it for 24 hours. A user's shopping cart that updates on every interaction? Mark it non‑cacheable. Being deliberate about caching is one of the highest‑leverage performance optimizations available to you — it can reduce backend load by orders of magnitude for read‑heavy endpoints.


5. Layered System — Clients Don't Need to Know the Details

A client calling https://api.example.com/products doesn't know (and shouldn't need to know) whether it's talking directly to your application server, through a load balancer, or via a CDN edge node. Intermediate layers — caches, gateways, security proxies, load balancers — can be inserted transparently without the client needing any changes.

Layer What it does Transparent to client?
Load balancer Distributes requests across multiple server instances ✅ Yes
API gateway Authentication, rate limiting, request routing ✅ Yes
CDN / edge cache Serves cached responses from servers close to the user ✅ Yes
Reverse proxy SSL termination, compression, logging ✅ Yes

The key rule: each layer only knows about the layer immediately adjacent to it. This lets you add cross‑cutting concerns (logging, security, rate limiting) to your entire API without touching your application code.


HTTP Status Codes — Speaking the Language of HTTP

Proper use of HTTP status codes is part of the uniform interface constraint — they tell clients exactly what happened without parsing the response body:

Status Meaning When to use
200 OK Success GET, PUT, PATCH returned data
201 Created Resource created Successful POST
204 No Content Success, no body Successful DELETE
400 Bad Request Invalid input Validation errors, malformed JSON
401 Unauthorized Not authenticated Missing or invalid token
403 Forbidden Not authorized Valid token, but not allowed this action
404 Not Found Resource doesn't exist GET/DELETE on a non‑existent ID
500 Internal Server Error Server bug Unexpected exceptions

Pro tip: Use 422 Unprocessable Entity for validation errors where the request syntax is correct but the data is invalid — it's more specific than 400 and widely supported.


The Richardson Maturity Model — Measuring RESTfulness

Leonard Richardson proposed a model that grades an API's RESTfulness across four levels. It's a useful way to assess how well your API adheres to REST principles:

Level Description Example
Level 0 One URI, one HTTP method (usually POST), different operations indicated by payload POST /api with {"action":"getProduct","id":42}
Level 1 Resources (different URIs for different resources) GET /api/products/42
Level 2 HTTP verbs (GET, POST, PUT, DELETE) and status codes DELETE /api/products/42 → 204 No Content
Level 3 HATEOAS — hypermedia controls in responses Response includes {"_links": {"self": "/products/42", "update": {...}}}

Most real‑world APIs are at Level 2 — they use resources and HTTP methods correctly. Level 3 (HATEOAS) is rare but powerful.


HATEOAS — Hypermedia as the Engine of Application State

HATEOAS is the final constraint of the uniform interface. It means that responses should contain links to related actions, allowing clients to discover the API dynamically.

{
  "id": 42,
  "name": "Widget",
  "price": 9.99,
  "_links": {
    "self": {
      "href": "https://api.example.com/products/42"
    },
    "update": {
      "href": "https://api.example.com/products/42",
      "method": "PUT"
    },
    "delete": {
      "href": "https://api.example.com/products/42",
      "method": "DELETE"
    }
  }
}

With HATEOAS, the client doesn't need to know that it can delete a product by sending DELETE /products/42 – the server tells the client via the hypermedia link. This makes the API self‑documenting and allows the server to change the URLs without breaking clients (as long as the link relation stays the same).


Filtering, Sorting, and Pagination

When returning collections, always provide filtering, sorting, and pagination as query parameters:

// Filtering
GET /api/products?category=electronics&priceMin=10

// Sorting
GET /api/products?sort=price,desc

// Pagination
GET /api/products?page=2&size=20

// Combined
GET /api/products?category=electronics&sort=price,desc&page=0&size=20

For filtering, support:

  • Equality?category=electronics
  • Range?priceMin=10&priceMax=100
  • Text search?search=laptop
  • Boolean flags?inStock=true

Structured Error Responses

When an error occurs, return a structured JSON error body that helps developers debug:

{
  "timestamp": "2024-02-15T10:30:00Z",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "path": "/api/products",
  "errors": [
    {
      "field": "price",
      "message": "Price must be greater than 0",
      "rejectedValue": -5
    },
    {
      "field": "name",
      "message": "Name is required"
    }
  ]
}

Consistent error format makes client‑side error handling much easier.


API Versioning — Managing Change

The REST spec doesn't mandate versioning, but it's essential in practice. Once clients depend on your API, breaking changes need a migration path. Common versioning strategies:

Strategy Example Pros / Cons
URI version /api/v1/products ✅ Explicit, easy to route
❌ Can clutter URIs
Header version Accept: application/json; version=1 ✅ Clean URIs
❌ Less visible in logs
Query parameter /api/products?v=1 ✅ Easy to test
❌ Can be cached incorrectly

Recommendation: Use URI versioning (/api/v1/...) – it's the most explicit and easiest to debug.

Run both versions simultaneously while clients migrate. When v1 traffic drops to zero, retire it.


REST API Best Practices Checklist

  • ✅ Use plural nouns for resource names: /products, /users.
  • ✅ Use HTTP methods correctly: GET (read), POST (create), PUT (replace), PATCH (partial update), DELETE (remove).
  • ✅ Use proper status codes – don't return 200 with an error message.
  • ✅ Return 201 Created with a Location header for POST.
  • ✅ Use query parameters for filtering, sorting, and pagination.
  • ✅ Use JSON as the default format with Content-Type: application/json.
  • ✅ Return consistent error structures (not just a string message).
  • ✅ Use verbs for non‑CRUD operations: /api/products/42/publish (POST).
  • ✅ Use nested resources for relationships: /api/users/123/orders.
  • Document your API – use OpenAPI / Swagger.
  • Version your API – allow clients to migrate gradually.
  • Use HTTPS – always encrypt traffic in production.

Common REST Anti‑Patterns to Avoid

  • Verbs in URIs/getProducts → use GET /products instead.
  • Returning 200 for errors – use the correct status code (400, 404, 500, etc.).
  • Not using HTTP cache headers – missed performance opportunity.
  • Nested resources too deep/users/123/orders/456/items/789 is too deep. Use query params for filtering.
  • Inconsistent response formats – some endpoints return arrays, others objects, some wrap in data.
  • No pagination for collectionsGET /products must paginate when the dataset is large.
  • Using PUT for partial updates – use PATCH for partial updates.

Summary

REST's five constraints — uniform interface, client‑server separation, statelessness, cacheability, and layered system — aren't arbitrary rules. Each one solves a real problem that plagued earlier distributed systems. When you design an API that follows these constraints, you get scalability, simplicity, and interoperability almost for free.

Start with the basics:

  • Use plural nouns for URIs.
  • Let HTTP verbs express actions.
  • Send proper status codes.
  • Provide filtering, sorting, and pagination on collections.
  • Return structured error responses.
  • Use versioning to manage change.

Everything else follows naturally from these principles. The result? An API that developers actually enjoy using — one that's predictable, self‑documenting, and a pleasure to integrate with.


Happy API designing!

Spring Boot REST API example

REST is an acronym for REpresentational State Transfer. In this article, we will walk through a sample REST application built using Spring Boot. This application does not use any database to store data, all the data is in memory. We will expose a few REST endpoints. Main purpose of this article is to demonstrate how to build REST API's using Spring boot.

Application Details

This application is about building a backend for a Blog software and exposing two REST API endpoints.
Following two endpoints both returns response in JSON format.
/blogapi/blogs : Returns list of blogs
/blogapi/blogs/ID : Returns details of a specific blog
Technology Used
  • Spring Boot 2.2.6.RELEASE
  • Logback 1.2.3
  • Maven 3
  • Java 11
  • Jackson 2.10.3

Project Structure


Code Reference

Maven pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<artifactId>com.bootng.rest</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>Spring Boot Rest</name>
	<description>Springboot Rest App</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.6.RELEASE</version>
		<relativePath />
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- junit 5 -->
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-core</artifactId>
			<version>2.19.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<addResources>true</addResources>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>
BlogStory Model
BlogStory model class which represent a blog entry.
package com.bootng.model;

public class BlogStory {
  private String id;
  private String name;
  private String summary;
  private String description;
  private String category;
  public BlogStory() {
  }
  public BlogStory (String name, String category, String summary) {
    this.id = name.replaceAll(" ","_");
    this.name = name;
    this.summary = summary;
    this.category = category;
    this.description = summary + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt "
        + "ut labore et dolore magna aliqua. "
        + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
        + " Duis aute irure dolor in reprehenderit in voluptate velit esse cillum "
        + "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
        + "sunt in culpa qui officia deserunt mollit anim id est laborum" ;   
  }
// Setters
 
}
BlogService Class
Service class which returns list of available blogs. Blog with an id etc.
@Service
public class BlogService {
  List category = Arrays.asList("Technical", "Travel", "Food", "Finance", "Entertainment");
  List stories = new ArrayList();
  {
    stories.add(new BlogStory("Java 11", "Technical", "Java 11 Blog"));
    stories.add(new BlogStory("Java 14", "Technical", "Java 14 Blog"));
    stories.add(new BlogStory("Asia Travel", "Travel", "Places to visit in Asia"));
    stories.add(new BlogStory("Europe Travel", "Travel", "Places to visit in Europe"));
    stories.add(new BlogStory("Japan Travel", "Travel", "Places to visit in Japan"));
    stories.add(new BlogStory("Asian Food", "Food", "Asian Food......"));
  }

  public BlogStory getBlogStory(String id) throws AppException {
    return stories.stream().filter(story -> id.equals(story.getId())).findAny().orElse(null);
  }

  public List getBlogStory() throws AppException {
    return stories;
  }

  public void addStory(BlogStory newStory) throws AppException {
    this.stories.add(newStory);
  }

  public List getBlogTags() throws AppException {
    return category;
  }
}
Main Application Class
Spring Boot's main application class.
@ComponentScan({"com.bootng"})
@SpringBootApplication
public class RestApplication {
  public static void main(String args[]) {
    SpringApplication.run(RestApplication.class, args);
  }
}
BlogAPIController Controller Class
Controller class which exposes the two endpoints
GET /blogapi/blogs
and GET /blogapi/blogs/ID
@Controller annotation is used to mark this class as a Controller.
@RequestMapping is used to map the request paths "/blogs" to getBlogSotries method and /blog/ID to getBlogStory method.
In both cases we are mapping both the paths to HTTP GET verb by using method = RequestMethod.GET
@ResponseBody is used to convert the result to JSON (as we specified by produces=MediaType.APPLICATION_JSON_VALUE)
@Controller
@RequestMapping("/blogapi")
public class BlogAPIController {
private static final Logger log = LoggerFactory.getLogger(BlogAPIController.class);
@Autowired
BlogService blogService;
ResponseEntity apiResponse = new ResponseEntity(newStory, HttpStatus.OK);
    return apiResponse;
}

@RequestMapping(value = {"/blogs"}, method = RequestMethod.GET,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public @ResponseBody ResponseEntity> getBlogStories() {
    log.info("inside getBlogStories GET method");
    List blogStory = null;
    ResponseEntity> apiResponse;
    try {
      blogStory = blogService.getBlogStory();
      apiResponse = new ResponseEntity>(blogStory, HttpStatus.OK);
    } catch (AppException e) {
      apiResponse =
          new ResponseEntity>(blogStory, HttpStatus.INTERNAL_SERVER_ERROR);
      e.printStackTrace();
    }
    return apiResponse;
  }

@RequestMapping(value = {"/blogs"}, method = RequestMethod.GET,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public @ResponseBody ResponseEntity getBlogStory(@PathParam(value = "") String id) {
    log.info("inside blog GET method");
    BlogStory blogStory = null;
    ResponseEntity apiResponse;
    try {
      blogStory = blogService.getBlogStory(id);
      if (blogStory == null)
        apiResponse = new ResponseEntity(blogStory, HttpStatus.NOT_FOUND);
      else
        apiResponse = new ResponseEntity(blogStory, HttpStatus.OK);
    } catch (AppException e) {
      apiResponse = new ResponseEntity(blogStory, HttpStatus.INTERNAL_SERVER_ERROR);
      e.printStackTrace();
    }
    return apiResponse;
  }
}

Start Application

Run Application
Build the project using mvn clean install and then run it with mvn spring-boot:run
mvn clean install
mvn spring-boot:run

Call REST API's using CURL

CURL: Get list of Blogs
Using curl we can get list of blogs from the /blogs endpoint
curl -X GET  http://localhost:8080/blogapi/blogs 
CURL: Get specific blog details
Get a specific blog with id "Java_14" by calling /blogs/Java_14
curl -X GET localhost:8080/blogapi/blogs/Java_14
Git Source Code
  • git clone https://github.com/siddharthagit/spring-boot-references
  • cd springboot-rest
  • mvn clean install
  • mvn spring-boot:run

    Conclusion

    In this article, we used Springs RestController annotation to build the API. With Spring boot we can use other frameworks like Jersey, Restlet, etc also to build API.