JavaSpringTechnology

How to Write Your Own Spring Boot REST Service

In this article, we will write our own Spring Boot REST Service from scratch. Spring Boot’s auto-configuration comes handy when you want to concentrate on your Business rather than spending time on writing framework components and boiler plate code.

Today, we will create a Simple RESTful Service. Let’s write a Dog Service. The quickest way to get on with a Spring Boot project is to use SPRING INITIALIZR.

1. Create Spring Boot Project

Let’s write our basic project components.

1.1 build.gradle

As of now we have added spring-boot-starter-web – denoting we want to build web application – and Lombok dependencies.

Lombok helps a lot getting rid of typical boiler plate code and I always love using it.
I generally use Lombok to avoid writing Constructors, Getters and Setters etc.
Visit Project Lombok for more information.

buildscript {
    ext {
        springBootVersion = '2.1.0.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.amitph.spring'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    compile('org.projectlombok:lombok:1.18.4')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

1.2 Application.java

Here is our starting point Application.java has the friendly public static void main method. Where we actually start a Spring Boot Application.

package com.amitph.spring.dogs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

At this point, you have your Spring Boot application up and running. You can confirm by running the Application.java like any other Java class and see the server being up and listening for port 8080 (configurable).

2. Mock Data Provider

First let’s start and create Service and Data layer the application. As mentioned above, we are not going to have a real datastore here in the tutorial, but we will use a mock data provider which will act as a datastore for our needs.

2.1 MockDogProvider

Mock Dog Provider has an instance level pre-populated list of Dogs. The various CRUD methods in the class, actually perform the operation on the Dogs List.

package com.amitph.spring.dogs.repo;

import com.amitph.spring.dogs.model.DogDto;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class MockDogProvider {
    private List<Dog> mockDogStore;

    public MockDogProvider() {
        mockDogStore = new ArrayList<>();
        mockDogStore.add(Dog.of(1, "Benji", 10));
        mockDogStore.add(Dog.of(2, "Baxter", 9));
        mockDogStore.add(Dog.of(3, "Brinkley", 8));
        mockDogStore.add(Dog.of(4, "Daisy", 10));
        mockDogStore.add(Dog.of(5, "Cujo", 12));
    }

    public List<Dog> getDogs() {
        return mockDogStore;
    }

    public Dog findDogById(long id) {
        for (Dog dog : mockDogStore) {
            if (dog.getId() == id) {
                return dog;
            }
        }
        return null;
    }

    public void add(DogDto dto) {
        mockDogStore.add(Dog.of(dto.getId(), dto.getName(), dto.getAge()));
    }

    public void delete(long id) {
        int idx = 0;
        for (; idx < mockDogStore.size(); idx++) {
            if (mockDogStore.get(idx).getId() == id) {
                break;
            }
        }
        mockDogStore.remove(idx);
    }
}

2.2 DogsService

Dogs service is a routine Service Layer of our web application. It just decouples controller and data provider. Because no business logic is involved, it just delegates the calls to Data Provider.

package com.amitph.spring.dogs.service;

import com.amitph.spring.dogs.model.DogDto;
import com.amitph.spring.dogs.repo.Dog;
import com.amitph.spring.dogs.repo.MockDogProvider;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
@Setter
public class DogsService {
    @Autowired private final MockDogProvider mockDogProvider;

    public void add(DogDto dto) {
        mockDogProvider.add(dto);
    }

    public void delete(long id) {
        mockDogProvider.delete(id);
    }

    public List<Dog> getDogs() {
        return mockDogProvider.getDogs();
    }

    public Dog getDogById(long id) {
        return mockDogProvider.findDogById(id);
    }
}

3. Controller

We are almost there. What we need in the end is a controller which handles the REST calls and delegate to Service.

package com.amitph.spring.dogs.web;

import com.amitph.spring.dogs.model.DogDto;
import com.amitph.spring.dogs.repo.Dog;
import com.amitph.spring.dogs.service.DogsService;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 java.util.List;

@RestController
@RequestMapping("/dogs")
@RequiredArgsConstructor
@Setter
public class DogsController {
    @Autowired private final DogsService service;

    @GetMapping
    public List<Dog> getDogs() {
        return service.getDogs();
    }

    @PostMapping
    public void postDogs(@RequestBody DogDto dto) {
        service.add(dto);
    }

    @GetMapping("/{id}")
    public Dog getById(@PathVariable(required = true) long id) {
        return service.getDogById(id);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable(required = true) long id) {
        service.delete(id);
    }
}

4. Run the application

/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.0.RELEASE)

2018-11-28 10:04:21.905 INFO 2752 --- [ main] com.amitph.spring.dogs.Application : Starting Application on Amits-office-mac.local with PID 2752 (/Users/aphaltankar/Workspace/personal/dog-service/out/production/classes started by aphaltankar in /Users/aphaltankar/Workspace/personal/dog-service)
2018-11-28 10:04:21.908 INFO 2752 --- [ main] com.amitph.spring.dogs.Application : No active profile set, falling back to default profiles: default
2018-11-28 10:04:22.745 INFO 2752 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2018-11-28 10:04:22.760 INFO 2752 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-11-28 10:04:22.760 INFO 2752 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/9.0.12
2018-11-28 10:04:22.765 INFO 2752 --- [ main] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/aphaltankar/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2018-11-28 10:04:22.837 INFO 2752 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-11-28 10:04:22.837 INFO 2752 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 893 ms
2018-11-28 10:04:22.857 INFO 2752 --- [ main] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2018-11-28 10:04:22.861 INFO 2752 --- [ main] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-11-28 10:04:22.861 INFO 2752 --- [ main] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-11-28 10:04:22.861 INFO 2752 --- [ main] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'formContentFilter' to: [/*]
2018-11-28 10:04:22.861 INFO 2752 --- [ main] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2018-11-28 10:04:23.044 INFO 2752 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2018-11-28 10:04:23.194 INFO 2752 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-11-28 10:04:23.197 INFO 2752 --- [ main] com.amitph.spring.dogs.Application : Started Application in 1.75 seconds (JVM running for 2.44)
2018-11-28 10:05:29.420 INFO 2752 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2018-11-28 10:05:29.420 INFO 2752 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2018-11-28 10:05:29.428 INFO 2752 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms

4.1 Get All Dogs

Open the browser and execute (GET)
http://localhost:8080/dog

And below is the output

[
   {
      "id":1,
      "name":"Benji",
      "age":10
   },
   {
      "id":2,
      "name":"Baxter",
      "age":9
   },
   {
      "id":3,
      "name":"Brinkley",
      "age":8
   },
   {
      "id":4,
      "name":"Daisy",
      "age":10
   },
   {
      "id":5,
      "name":"Cujo",
      "age":12
   }
]

4.2 Get Dog by Id

Let’s make a GET request

http://localhost:8080/dogs/3

And below is the output

{
   "id":3,
   "name":"Brinkley",
   "age":8
}

4.3 Add New Dog

This is a POST request I will use curl to make this call.

curl -X POST \
  http://localhost:8080/dogs \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: c6813aea-146c-49cd-9eba-1370aad4bff9' \
  -H 'cache-control: no-cache' \
  -d '{
    "id": 6,
    "name": "Hooch",
    "age": 11
  }'

After this execute the GET /dogs again and you will see a new dogs has been added.

4.4 Delete a Dog

This a DELETE request using curl here.

curl -X DELETE \
  http://localhost:8080/dogs/5 \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: b4b93304-7ee7-45c2-917b-c3bc2985a250' \
  -H 'cache-control: no-cache'

After this you should see Dog with id 5 is gone from the list

5 Summary

So we have created our Spring Boot Rest Example. We created a Dog Service with simple Create, GET and DELETE endpoints. We have seen how easy a service is with Spring Boot where you can skip lot of crap and concentrate on your business.

In the next article we will refer to the Same Dog Service and learn Spring Rest Service Exception Handling. We will learn how spring takes care of all the crappy exceptions handling stuff lets us do more important things.

Many more articles to come, please comment for your queries and suggestions.


For full source code of the examples used here, please visit https://github.com/amitrp/dog-service


Leave a Reply

Your email address will not be published. Required fields are marked *