Showing posts with label spring-boot. Show all posts

Spring boot rest api with Mongodb CRUD examples

In previous articles we have seen Creating first rest api in spring-boot and also Passing and validating RequestParam in spring-boot rest api now we will take one step further by interacting with database. Also checkout Spring boot rest api with MySQL CRUD examples or browse all spring tutorials here.

build.gradle
update your build.gradle, add following dependencies and refresh the project, gradle will download the required dependencies(jar files).
//mongodb dependencies
compile('org.springframework.boot:spring-boot-starter-data-mongodb')
application.properties
Create new source folder under src/main/resources if not exist and add new file called application.properties. Add following lines for database connection.
#mongodb
spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.database=spring_boot_examples
spring.data.mongodb.username=root
spring.data.mongodb.password=root

Source code (User.java)
Note: In current example we are using spring boot 1.5.7.RELEASE which uses validations framework 1.1.0. @NotEmpty, @Email are part of package org.hibernate.validator. But spring boot 2.0.0 and above which supports validation framework 2.0 so @NotEmpty, @Email, etc... are now part of javax.validation.constraints.
import java.util.Date;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

/**
 * @author javaQuery
 * @since 2018-09-24
 * @github https://github.com/javaquery/spring-boot-examples
 */
@Document(collection = "users")
public class User{

    @Id
    public String id;

    @Field("first_name")
    @NotEmpty(message = "first_name can not be empty.")
    @Size(max = 100, message = "first_name can not be more than 100 characters.")
    private String firstName;
 
    @Field("last_name")
    @NotEmpty(message = "last_name can not be empty.")
    @Size(max = 100, message = "last_name can not be more than 100 characters.")
    private String lastName;
 
    @Field("email")
    @NotEmpty(message = "email can not be empty.")
    @Size(max = 100, message = "email can not be more than 100 characters.")
    private String email;
 
    @Field("password")
    @NotEmpty(message = "password can not be empty.")
    @Size(max = 100, message = "password can not be more than 100 characters.")
    private String password;
 
    @Field("created")
    private Long created = (new Date().getTime())/ 1000;
 
    @Field("modified")
    private Long modified  = (new Date().getTime())/ 1000;

    //getter-setter
}

Abstraction in Spring
Spring is all about abstraction via interfaces. Abstraction allows you to change the implementation without affecting caller method.

Repositories
The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
Spring Repository(interface) is abstraction for data access layer.

For example in your project you are going to use data storage like MySQL(RDBMS), MongoDb (NoSQL), etc... The caller class will interact to data storage using repository(interface) so for caller class it doesn't matter which storage you are using in implementation of repository either MySQL or MongoDb as long as data persist and retrieve back.

Here we are using MongoRepository for which spring will provide default implementation of MongoDb queries (INSERT, UPDATE, DELETE, SELECT) without writing it yourself.

Source code (MongoRepository.java)
import java.util.UUID;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.javaquery.examples.springboot.model.mongodb.User;

/**
 * @author javaQuery
 * @since 2018-09-24
 * @github https://github.com/javaquery/spring-boot-examples
 */
public interface UserMongoDbRepository extends MongoRepository<User, String>{
}
Now if you want to save record (User), all you got to do is call userMongoDbRepository.save(user);. Calling save on UserRepository will generate INSERT statement and execute it. Similarly you can perform update, select and delete.

Source code (Application.java)
Add @EnableMongoRepositories in your Application.java.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 */
@SpringBootApplication
@ComponentScan(basePackages = { "com.javaquery.examples.springboot.rest" })
@EnableMongoRepositories("com.javaquery.examples.springboot.model.mongodb.repositories")
public class Application {
    public static void main(String[] args) {
       SpringApplication.run(Application.class, args);
    }
}
  • @EnableMongoRepositories - Location(package) of your code which interact with database (repositories)

Services
Service is abstraction layer for application logic implementation.

Source code (UserMongoDbService.java)
UserMongoDbDTO / UserMongoDbDTO are request objects (JSON Payload). Source code is available under controller section.
import com.javaquery.examples.springboot.rest.dto.UserMongoDbDTO;
import com.javaquery.examples.springboot.rest.dto.UserUpdateDTO;

/**
 * @author javaQuery
 * @since 2018-09-24
 * @github https://github.com/javaquery/spring-boot-examples
 */
public interface UserMongoDbService {
    public UserMongoDbDTO addUser(UserMongoDbDTO userDTO);
    public UserMongoDbDTO getUser(String id);
    public UserMongoDbDTO updateUser(UserUpdateDTO userUpdateDTO, String id);
    public void deleteUser(String id);
}
Source code (UserMongoDbServiceImpl.java)
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.javaquery.examples.springboot.model.mongodb.User;
import com.javaquery.examples.springboot.model.mongodb.repositories.UserMongoDbRepository;
import com.javaquery.examples.springboot.rest.dto.UserMongoDbDTO;
import com.javaquery.examples.springboot.rest.dto.UserUpdateDTO;
import com.javaquery.examples.springboot.rest.exception.EntityNotFoundException;
import com.javaquery.examples.springboot.rest.service.UserMongoDbService;

/**
 * @author javaQuery
 * @since 2018-09-24
 * @github https://github.com/javaquery/spring-boot-examples
 */
@Service
public class UserMongoDbServiceImpl implements UserMongoDbService {

    @Autowired
    private UserMongoDbRepository userMongoDbRepository;

    @Override
    public UserMongoDbDTO addUser(UserMongoDbDTO userDTO) {
        /**
         * We are manually creating {@link User} object however there is mapper
         * available to convert to-from {@link UserDTO}.
         */
        User user = new User();
        user.setFirstName(userDTO.getFirstName());
        user.setLastName(userDTO.getLastName());
        user.setEmail(userDTO.getEmail());
        user.setPassword(userDTO.getPassword());
        userMongoDbRepository.save(user);

        /* set generated user id to response object */
        userDTO.setId(user.getId());
        userDTO.setPassword("");
        return userDTO;
    }

    @Override
    public UserMongoDbDTO getUser(String id) {
        User user = userMongoDbRepository.findOne(id);
        if (Objects.isNull(user)) {
            /* handle this exception using {@link RestExceptionHandler} */
            throw new EntityNotFoundException(User.class, id);
        }
        return new UserMongoDbDTO().build(user);
    }

    @Override
    public UserMongoDbDTO updateUser(UserUpdateDTO userUpdateDTO, String id) {
        User user = userMongoDbRepository.findOne(id);
        if (Objects.isNull(user)) {
            /* handle this exception using {@link RestExceptionHandler} */
            throw new EntityNotFoundException(User.class, id);
        }
        user.setFirstName(userUpdateDTO.getFirstName());
        user.setLastName(userUpdateDTO.getLastName());
        userMongoDbRepository.save(user);
        return new UserMongoDbDTO().build(user);
    }

    @Override
    public void deleteUser(String id) {
        userMongoDbRepository.delete(id);
    }
}

Controller (REST)
Controller is the entry point for all REST request.

Source code (UserMongoDbDTO.java)
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.javaquery.examples.springboot.model.mongodb.User;

/**
 * Object to interact using rest api.
 *
 * @author javaQuery
 * @since 2018-09-24
 * @github https://github.com/javaquery/spring-boot-examples
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class UserMongoDbDTO {

    /* used to send database id */
    private String id;

    @NotEmpty(message = "first_name can not be empty")
    @JsonProperty("first_name")
    private String firstName;

    @NotEmpty(message = "last_name can not be empty")
    @JsonProperty("last_name")
    private String lastName;

    @Email
    @JsonProperty("email")
    private String email;

    @NotEmpty(message = "password can not be empty")
    @Size(min = 6, message = "password must be at least 6 character")
    @JsonProperty("password")
    private String password;

    // getter - setter

    /**
     * We are manually creating {@link UserMongoDbDTO} object however there is
     * mapper available to convert to-from {@link User}.
     *
     * @param user
     * @return
     */
    public UserMongoDbDTO build(User user) {
        this.id = user.getId();
        this.firstName = user.getFirstName();
        this.lastName = user.getLastName();
        this.email = user.getEmail();
        return this;
    }
}

Source code (UserUpdateDTO.java)
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
 * Object to interact using rest api.
 * @author javaQuery
 * @since 2018-02-18
 * @github https://github.com/javaquery/spring-boot-examples
 */
public class UserUpdateDTO {
    @NotEmpty(message = "first_name can not be empty")
    @JsonProperty("first_name")
    private String firstName;
 
    @NotEmpty(message = "last_name can not be empty")
    @JsonProperty("last_name")
    private String lastName;
 
    // getter - setter
}

Source code (UserMongoDbController.java)
Http request POST, GET, PUT and POST are implemented in controller.
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.javaquery.examples.springboot.rest.dto.UserMongoDbDTO;
import com.javaquery.examples.springboot.rest.dto.UserUpdateDTO;
import com.javaquery.examples.springboot.rest.response.SuccessResponse;
import com.javaquery.examples.springboot.rest.service.UserMongoDbService;

/**
 * @author javaQuery
 * @since 2018-09-24
 * @github https://github.com/javaquery/spring-boot-examples
 */
@RestController
@RequestMapping("/api/user/mongodb")
public class UserMongoDbController {

    @Autowired
    private UserMongoDbService userMongoDbService;

    @PostMapping
    public ResponseEntity<UserMongoDbDTO> addUser(@Valid @RequestBody UserMongoDbDTO userDTO) {
        return ResponseEntity.ok(userMongoDbService.addUser(userDTO));
    }

    @GetMapping
    public ResponseEntity<UserMongoDbDTO> getUser(@RequestParam String id) {
        return ResponseEntity.ok(userMongoDbService.getUser(id));
    }

    @PutMapping
    public ResponseEntity<UserMongoDbDTO> updateUser(@Valid @RequestBody UserUpdateDTO userUpdateDTO, @RequestParam String id) {
        return ResponseEntity.ok(userMongoDbService.updateUser(userUpdateDTO, id));
    }

    @DeleteMapping
    public ResponseEntity<?> deleteUser(@RequestParam String id) {
        userMongoDbService.deleteUser(id);
        return ResponseEntity.ok(new SuccessResponse("deleted"));
    }
}
Note: Add updated EntityNotFoundException.java and RestExceptionHandler.java from github.

Request/Response
cURL POST http://localhost:8080/api/user/mongodb
-body {"first_name":"vicky","last_name":"thakor","email":"vicky.thakor@javaquery.com","password":"123456"}
-code 200
-body {"id":"5bada32eac0eb2127413b580","first_name":"vicky","last_name":"thakor","email":"vicky.thakor@javaquery.com","password":""}
cURL GET http://localhost:8080/api/user/mongodb?id=5bada32eac0eb2127413b580
-code 200
-body {"id":"5bada32eac0eb2127413b580","first_name":"vicky","last_name":"thakor","email":"vicky.thakor@javaquery.com"}
cURL PUT http://localhost:8080/api/user/mongodb?id=5bada32eac0eb2127413b580
-body {"first_name":"vickey","last_name":"thakor"}
-code 200
-body {"id":"5bada32eac0eb2127413b580","first_name":"vickey","last_name":"thakor","email":"vicky.thakor@javaquery.com"}
cURL DELETE http://localhost:8080/api/user/mongodb?id=5bada32eac0eb2127413b580
-code 200
-body {"message": "deleted"}

Spring internationalization(i18n) example for response and errors

Nowadays,  your application has users from around the world. For ease of your user, you want them to use web application in their local language. You must consider internationalization (i18n) while creating your web application.

In following example we will learn how you can apply internationalization to your spring application. In my previous articles we learned Creating first rest api in spring-boot and you can browse all spring article for starter.

Language specific properties file
Create properties file under src/main/resource folder for individual language you want to support in your application. We create package locale under folder and placed all file in it.

messages_de.properties
springboot.hello=Hallo
springboot.customerror=benutzerdefinierte Fehlermeldung
messages_en.properties
springboot.hello=Hello
springboot.customerror=custom error message
messages_fr.properties
springboot.hello=Bonjour
springboot.customerror=message d'erreur personnalisé
messages_zh.properties
For Chinese character you have to convert character / word into Unicode. How to convert Chinese character or word to Unicode online?
springboot.hello=\u4f60\u597d
springboot.customerror=\u81ea\u5b9a\u4e49\u9519\u8bef\u6d88\u606f

Source code (ApplicationConfig.java)
Load your language properties file in spring context using ReloadableResourceBundleMessageSource.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @author javaQuery
 * @since 2018-02-18
 * @github https://github.com/javaquery/spring-boot-examples
 */
@Configuration
public class ApplicationConfig extends WebMvcConfigurerAdapter{
    @Bean
    public ReloadableResourceBundleMessageSource messageSource(){
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:locale/messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
 
    /**
     * Internationalization for parameter and payload validation messages. 
     * @return
     */
    @Bean
    public LocalValidatorFactoryBean validator() {
       LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
       bean.setValidationMessageSource(messageSource());
       return bean;
    }

    @Override
    public Validator getValidator() {
       return validator();
    }
}

Source code (Application.java)
Add ApplicationConfig.java file package com.javaquery.examples.springboot.main.config in Application.java for component scan.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 * 
 * @change message internationalization (locale)
 */
@SpringBootApplication
@ComponentScan(basePackages = { "com.javaquery.examples.springboot.rest",  "com.javaquery.examples.springboot.main.config"})
@EntityScan("com.javaquery.examples.springboot.model")
@EnableJpaRepositories("com.javaquery.examples.springboot.model.repositories")
public class Application {
    public static void main(String[] args) {
       SpringApplication.run(Application.class, args);
    }
}

Source code (LocaleController.java)
import java.util.Locale;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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;

/**
 * Springboot internationalization example
 * @author javaQuery
 * @since 2018-04-06  
 */
@RestController
@RequestMapping("/api")
@Validated
public class LocaleController {
 
    @Autowired
    private MessageSource messageSource; 
 
    /**
     * Response internationalization.
     * @param name
     * @param locale
     * @return
     */
    @GetMapping("/locale")
    public ResponseEntity<?> sayHello(@RequestParam("name") String name, Locale locale){
       String localeHello = messageSource.getMessage("springboot.hello", null, locale);
       return ResponseEntity.ok(localeHello + " " + name);
    }
 
    /**
     * Validation parameter internationalization. 
     * @param firstname
     * @param lastname
     * @param notEmptyParam
     * @param locale
     * @return
     */
    @GetMapping("/locale/param")
    public ResponseEntity<?> parameterValidation(@Size(min = 1, max = 5, message = "firstname {javax.validation.constraints.Size.message}") @RequestParam("firstname") String firstname,
       @NotEmpty(message = "lastname {org.hibernate.validator.constraints.NotEmpty.message}") @RequestParam("lastname") String lastname,
       @NotEmpty(message = "{springboot.customerror}") String notEmptyParam, Locale locale){
         String localeHello = messageSource.getMessage("springboot.hello", null, locale);
         return ResponseEntity.ok(localeHello + " " + firstname + " " + lastname);
    }
}
We used properties file key springboot.hello to send language specific response of "Hello".

Internationalization for error messages
parameterValidation method demonstrate use of internationalization code for validation.
 - You can create your own custom code like {springboot.customerror}.
 - Use inbuilt codes. You find out internationalization code from source of annotation.


Enable Internationalization
To enable internationalization for http request send header accept-language in your request.

Request/Response
cURL POST localhost:8080/api/locale?name=vicky
-H 'accept-language: fr'
-code 200
-body Bonjour vicky
cURL POST localhost:8080/api/locale/param?firstname=VickyV&lastname=Thakor
-H 'accept-language: de'
-code 400
-body {"errors":["firstname muss zwischen 1 und 5 liegen","benutzerdefinierte Fehlermeldung"]}

Spring boot rest api with MySQL CRUD examples


In previous articles we have seen Creating first rest api in spring-boot and also Passing and validating RequestParam in spring-boot rest api now we will take one step further by interacting with database. Also checkout Spring boot rest api with Mongodb CRUD examples or browse all spring tutorials here.

Project structure

build.gradle
update your build.gradle, add following dependencies and refresh the project, gradle will download the required dependencies(jar files).
// database dependencies
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtime('mysql:mysql-connector-java')
application.properties
Create new source folder under src/main/resources if not exist and add new file called application.properties. Add following lines for database connection.
#datasource configurations
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_examples
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true
Now create table users in database. Create table sql script is available on github.

Source code (User.java)
We used DatabaseEntity and ModifiableEntity interfaces from Java Interfaces. Also we used Java Constraint Validation on each column.
Note: In current example we are using spring boot 1.5.7.RELEASE which uses validations framework 1.1.0. @NotEmpty, @Email are part of package org.hibernate.validator. But spring boot 2.0.0 and above which supports validation framework 2.0 so @NotEmpty, @Email, etc... are now part of javax.validation.constraints.
import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Size;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.validator.constraints.NotEmpty;
/**
 * @author javaQuery
 * @since 2018-02-18
 * @github https://github.com/javaquery/spring-boot-examples
 */
@Entity
@Table(name = "users")
public class User implements DatabaseEntity, ModifiableEntity{
    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Column(name = "id", columnDefinition = "BINARY(16)")
    private UUID id;
   
    @Column(name = "first_name")
    @NotEmpty(message = "first_name can not be empty.")
    @Size(max = 100, message = "first_name can not be more than 100 characters.")
    private String firstName;
    
    @Column(name = "last_name")
    @NotEmpty(message = "last_name can not be empty.")
    @Size(max = 100, message = "last_name can not be more than 100 characters.")
    private String lastName;
    
    @Column(name = "email")
    @NotEmpty(message = "email can not be empty.")
    @Size(max = 100, message = "email can not be more than 100 characters.")
    private String email;
    
    @Column(name = "password")
    @NotEmpty(message = "password can not be empty.")
    @Size(max = 100, message = "password can not be more than 100 characters.")
    private String password;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created")
    private Date created = new Date();
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "modified")
    private Date modified  = new Date();
    
    //getter-setter
}

Abstraction in Spring
Spring is all about abstraction via interfaces. Abstraction allows you to change the implementation without affecting caller method.

Repositories
The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
Spring Repository(interface) is abstraction for data access layer.

For example in your project you are going to use data storage like MySQL(RDBMS), MongoDb (NoSQL), etc... The caller class will interact to data storage using repository(interface) so for caller class it doesn't matter which storage you are using in implementation of repository either MySQL or MongoDb as long as data persist and retrieve back.

Here we are using JpaRepository for which spring will provide default implementation of basic SQL queries (INSERT, UPDATE, DELETE, SELECT) without writing it yourself.

Source code (UserRepository.java)
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import com.javaquery.examples.springboot.model.User;
/**
 * @author javaQuery
 * @since 2018-02-18
 */
public interface UserRepository extends JpaRepository<User, UUID>{
}
Now if you want to save record (User), all you got to do is call userRepository.save(user);. Calling save on UserRepository will generate INSERT statement and execute it. Similarly you can perform update, select and delete.

Source code (Application.java)
Add @EntityScan and EnableJpaRepositories in your Application.java.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 */
@SpringBootApplication
@ComponentScan(basePackages = { "com.javaquery.examples.springboot.rest" })
@EntityScan("com.javaquery.examples.springboot.model")
@EnableJpaRepositories("com.javaquery.examples.springboot.model.repositories")
public class Application {
    public static void main(String[] args) {
       SpringApplication.run(Application.class, args);
    }
}
  • @EntityScan - Location(package) of your database entities
  • @EnableJpaRepositories - Location(package) of your code which interact with database (repositories)

Services
Service is abstraction layer for application logic implementation.

Source code (UserService.java)
UserDTO / UserUpdateDTO are request objects (JSON Payload). Source code is available under controller section.
import java.util.UUID;
import com.javaquery.examples.springboot.rest.dto.UserDTO;
import com.javaquery.examples.springboot.rest.dto.UserUpdateDTO;
/**
 * @author javaQuery
 * @since 2018-02-18
 * @github https://github.com/javaquery/spring-boot-examples
 */
public interface UserService {
    public UserDTO addUser(UserDTO userDTO);
    public UserDTO getUser(UUID id);
    public UserDTO updateUser(UserUpdateDTO userUpdateDTO, UUID id);
    public void deleteUser(UUID id);
}
Source code (UserServiceImpl.java)
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.javaquery.examples.springboot.model.User;
import com.javaquery.examples.springboot.model.repositories.UserRepository;
import com.javaquery.examples.springboot.rest.dto.UserDTO;
import com.javaquery.examples.springboot.rest.dto.UserUpdateDTO;
import com.javaquery.examples.springboot.rest.exception.EntityNotFoundException;
import com.javaquery.examples.springboot.rest.service.UserService;
/**
 * @author javaQuery
 * @since 2018-02-18
 * @github https://github.com/javaquery/spring-boot-examples
 */
@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserRepository userRepository;
 
    @Override
    public UserDTO addUser(UserDTO userDTO) {
    Date now = new Date();
  
    /**
     * We are manually creating {@link User} object however there is mapper available  to convert to-from {@link UserDTO}.
     */
    User user = new User();
    user.setFirstName(userDTO.getFirstName());
    user.setLastName(userDTO.getLastName());
    user.setEmail(userDTO.getEmail());
    user.setPassword(userDTO.getPassword());
    user.setCreated(now);
    user.setModified(now);
    userRepository.save(user);
 
    /* set generated user id to response object */
    userDTO.setId(user.getId());
    userDTO.setPassword("");
    return userDTO;
   }

   @Override
   public UserDTO getUser(UUID id) {
    User user = userRepository.getOne(id);
    if(Objects.isNull(user)){
     /* handle this exception using {@link RestExceptionHandler} */
     throw new EntityNotFoundException(User.class, id);
    }
    return new UserDTO().build(user);
   }

   @Override
   public UserDTO updateUser(UserUpdateDTO userUpdateDTO, UUID id) {
    User user = userRepository.getOne(id);
    if(Objects.isNull(user)){
     /* handle this exception using {@link RestExceptionHandler} */
     throw new EntityNotFoundException(User.class, id);
    }
    user.setFirstName(userUpdateDTO.getFirstName());
    user.setLastName(userUpdateDTO.getLastName());
    userRepository.save(user);
    return new UserDTO().build(user);
   }

   @Override
   public void deleteUser(UUID id) {
    userRepository.delete(id);
   }
}

Controller (REST)
Controller is the entry point for all REST request.

Source code (UserDTO.java)
import java.util.UUID;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.javaquery.examples.springboot.model.User;
/**
 * Object to interact using rest api.
 * @author javaQuery
 * @since 2018-02-18
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class UserDTO {
 
    /* used to send database id */
    private UUID id;
 
    @NotEmpty(message = "first_name can not be empty")
    @JsonProperty("first_name")
    private String firstName;
 
    @NotEmpty(message = "last_name can not be empty")
    @JsonProperty("last_name")
    private String lastName;
    
    @Email
    @JsonProperty("email")
    private String email;
    
    @NotEmpty(message = "password can not be empty")
    @Size(min = 6, message = "password must be at least 6 character")
    @JsonProperty("password")
    private String password;
   
    // getter - setter
    
    /**
     * We are manually creating {@link UserDTO} object however there is mapper available 
     * to convert to-from {@link User}.
     * @param user
     * @return
     */
    public UserDTO build(User user){
     this.id = user.getId();
     this.firstName = user.getFirstName();
     this.lastName = user.getLastName();
     this.email = user.getEmail();
     return this;
    }
}

Source code (UserUpdateDTO.java)
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
 * Object to interact using rest api.
 * @author javaQuery
 * @since 2018-02-18
 */
public class UserUpdateDTO {
    @NotEmpty(message = "first_name can not be empty")
    @JsonProperty("first_name")
    private String firstName;
 
    @NotEmpty(message = "last_name can not be empty")
    @JsonProperty("last_name")
    private String lastName;
 
    // getter - setter
}

Source code (UserController.java)
Http request POST, GET, PUT and POST are implemented in controller.
import java.util.UUID;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.javaquery.examples.springboot.rest.dto.UserDTO;
import com.javaquery.examples.springboot.rest.dto.UserUpdateDTO;
import com.javaquery.examples.springboot.rest.response.SuccessResponse;
import com.javaquery.examples.springboot.rest.service.UserService;
/**
 * @author javaQuery
 * @since 2018-02-18
 */
@RestController
@RequestMapping("/api/user")
public class UserController {
 
    @Autowired
    private UserService userService; 
 
    @PostMapping
    public ResponseEntity<UserDTO> addUser(@Valid @RequestBody UserDTO userDTO){
     return ResponseEntity.ok(userService.addUser(userDTO));
    }
 
    @GetMapping
    public ResponseEntity<UserDTO> getUser(@RequestParam UUID id){
     return ResponseEntity.ok(userService.getUser(id));
    }
 
    @PutMapping
    public ResponseEntity<UserDTO> updateUser(@Valid @RequestBody UserUpdateDTO userUpdateDTO, @RequestParam UUID id){
     return ResponseEntity.ok(userService.updateUser(userUpdateDTO, id));
    }
 
    @DeleteMapping
    public ResponseEntity<?> deleteUser(@RequestParam UUID id){
     userService.deleteUser(id);
     return ResponseEntity.ok(new SuccessResponse("deleted"));
    }
}

Note: Add updated EntityNotFoundException.java and RestExceptionHandler.java from github.

Request/Response
cURL POST http://localhost:8080/api/user
-body {"first_name":"vicky","last_name":"thakor","email":"vicky.thakor@javaquery.com","password":"123456"}
-code 200
-body {"id":"95572033-9cf1-421a-94a7-dc4cdf7e9c71","first_name":"vicky","last_name":"thakor","email":"vicky.thakor@javaquery.com","password":""}
cURL GET http://localhost:8080/api/user?id=95572033-9cf1-421a-94a7-dc4cdf7e9c71
-code 200
-body {"id":"95572033-9cf1-421a-94a7-dc4cdf7e9c71","first_name":"vicky","last_name":"thakor","email":"vicky.thakor@javaquery.com"}
cURL PUT http://localhost:8080/api/user?id=95572033-9cf1-421a-94a7-dc4cdf7e9c71
-body {"first_name":"chirag","last_name":"thakor"}
-code 200
-body {"id":"95572033-9cf1-421a-94a7-dc4cdf7e9c71","first_name":"chirag","last_name":"thakor","email":"vicky.thakor@javaquery.com"}
cURL DELETE http://localhost:8080/api/user?id=95572033-9cf1-421a-94a7-dc4cdf7e9c71
-code 200
-body {"message": "deleted"}

Constraints validation for user inputs (javax.validation.constraints)

User input validation is common part of any application. JBoss developers made it easy to validate inputs using Java Bean Validation framework.

Bean Validation 2.0 (http://beanvalidation.orgJSR 380)
We will be using Bean Validation framework 2.0 for the example. Its certified implementation is Hibernate Validator 6.0.1.Final or above. Framework 2.0 require Java 8 or higher version.

Source code (User.java)
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

/**
 * @author javaQuery
 * @since 2018-02-24
 * @github: https://github.com/javaquery/Examples
 */
public class User {

    @NotEmpty(message = "firstName can not be empty")
    @Size(min = 2, max = 20, message = "firstName length must be between 2 and 20")
    private String firstName;

    @NotEmpty(message = "lastName can not be empty")
    @Size(min = 2, max = 20, message = "lastName length must be between 2 and 20")
    private String lastName;

    @NotEmpty(message = "nickNames can not be empty")
    private List<@Size(min = 2, message = "nickName length must be greater than 2") String> nickNames;

    @Email
    private String email;

    @NotEmpty(message = "password can not be empty")
    @Size(min = 6, message = "password length must be at least 6 character")
    private String password;

    // getter-setter
}
  • @NotEmpty - We are using it to check string can not be empty. Can be used with collection, map or array.
  • @Size - We are using it to check the length of string. Can be used with collection, map or array.
  • @Email - The string has to be a well-formed email address.
There are other annotations like @NotNull, @Min, @Max@Past, @Future, @PastOrPresent, @FutureOrPresent, etc... Complete list of annotations is available on Bean Validation specification.

Source code (ValidatorFactoryExample.java)
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

/**
 * @author javaQuery
 * @since 2018-02-24
 * @github: https://github.com/javaquery/Examples
 */
public class ValidatorFactoryExample {

    public static void main(String[] args) {
        User user = new User();
        user.setFirstName("Vicky");
        user.setEmail("not-an-email");
        user.setNickNames(Arrays.asList("vicks", "v"));
        user.setPassword("1234567");
        
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        /* validate object and get constraints failed */
        Set<ConstraintViolation<User>> violations = validator.validate(user);
        
        for (ConstraintViolation<User> violation : violations) {
            System.err.println(violation.getMessage());
        }
    }
}

Output
nick name lenght must be greater than 2
lastName can not be empty
must be a well-formed email address

Other frameworks like Spring triggers validation using annotation so you don't have to validate bean on your own like we did using ValidatorFactory.

Similar
Passing and validating RequestParam in spring-boot rest api

Passing and validating RequestParam in spring-boot rest api

In my previous article Creating first rest api in spring-boot we learned how you can create rest api. Now lets learn how you can pass parameters to api and validate it. In this example I'm going to use code from my previous article or you can head to Spring Tutorials page.

@RequestParam
Annotation which indicates that a method parameter should be bound to a web request parameter.

Source code (HelloWorldController.java)
import javax.validation.constraints.Size;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 */
@RestController
@RequestMapping("/api")
@Validated
public class HelloWorldController {
 
 /**
  * This will send Hello {name-from-parameter} in response with HTTP status code 200.
  * @since 2018-02-01
  * @param firstname
  * @param lastname
  * @return
  */
   @GetMapping("/hello")
   public ResponseEntity<?> sayHello(
     @RequestParam @Size(min= 1, max = 5, message = "firstname length must be between 1 and 5") String firstname,
     @RequestParam String middlename,
     @RequestParam(required = false) String lastname){
    /* check lastname value */
    lastname = lastname != null ? lastname : "{lastname-is-optional}";
    return ResponseEntity.ok("Hello " + firstname + " " + middlename + " " + lastname);
   }
}
Request parameter syntax
Using @RequestParam spring rest api accept parameters from the request. You can also use various annotation from package javax.validation.constraints with @RequestParam.
@RequestParam <data_type> <variable_name>;

Lets quickly understand annotations used in HelloWorldController.java. As we already discussed few annotations in Creating first rest api in spring-boot so we will talk about new annotations only.

  • @Validated - To perform validation on each method of controller if any.
  • @RequestParam - To accept web request parameter in variable. (Note: All variable annotated with @RequestParam are compulsory/mandatory for request, until you set required = false @RequestParam(required = false) for that parameter)
Request parameter variations
@RequestParam @Size(min= 1, max = 5 , message = "firstname length must be between 1 and 5") String firstname
- @Size of package javax.validation.constraints is used to validate length of parameter in request and will throw ConstraintViolationException if data is not valid. You can read more about Constraints validation for user inputs.
@RequestParam String middlename
- Accept mandatory parameter in variable middlename. If parameter is not present in request then spring will throw MissingServletRequestParameterException.
@RequestParam(required = false) String lastname
- Accept optional parameter in variable lastname.

Exception Handling
For exception org.springframework.web.bind.MissingServletRequestParameterException and javax.validation.ConstraintViolationException, spring will send 500 (Internal Server Error) in response so we'll catch those exception and send customized response.

Source code (RestExceptionHandler.java)
Used to catch exception thrown.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/**
 * @author javaQuery
 * @since2018-02-01
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice()
public class RestExceptionHandler extends ResponseEntityExceptionHandler{
 
   @Override
   protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers,
     HttpStatus status, WebRequest request) {
    String error = ex.getParameterName() + " parameter is missing.";
    return new ResponseEntity<Object>(new ErrorResponse<>(Arrays.asList(error)), HttpStatus.BAD_REQUEST);
   }
 
   /**
    * Exception thrown when {@link org.springframework.validation.annotation.Validated} is used in controller.
    * @param ex
    * @param request
    * @return
    */
   @ExceptionHandler(ConstraintViolationException.class)
   protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
    try {
     List<String> messages = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
     return new ResponseEntity<>(new ErrorResponse<>(messages), HttpStatus.BAD_REQUEST);
    } catch (Exception e) {
     return new ResponseEntity<>(new ErrorResponse<>(Arrays.asList(ex.getMessage())), HttpStatus.INTERNAL_SERVER_ERROR);
    }
   }
}
Source code (ErrorResponse.java)
Used to wrap the error message for response.
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

/**
 * @author javaQuery
 * @since 2018-02-01
 * 
 * @param <T>
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class ErrorResponse<T> {
   private List<T> errors = new ArrayList<>(1);
 
   public ErrorResponse(List<T> errors) {
    this.errors = errors;
   }
 
   public List<T> getErrors() {
    return errors;
   }
}

Request/Response
cURL GET http://localhost:8080/api/hello
-code 400
-body {"errors":["firstname parameter is missing."]}
cURL GET http://localhost:8080/api/hello?firstname=&middlename=v
-code 400
-body {"errors":["firstname length must be between 1 and 5"]}
cURL GET http://localhost:8080/api/hello?firstname=vicky&middlename=v
-code 200
-body Hello vicky v {lastname-is-optional}
cURL GET http://localhost:8080/api/hello?firstname=vicky&middlename=v&lastname=thakor
-code 200
-body Hello vicky v thakor

Creating first rest api in spring-boot

spring-boot
Today we are going to learn how to create rest api in spring-boot. This example is created using gradle, if you are not familiar with how to setup gradle project in eclipse follow the steps stated in Setup first gradle project in eclipse

build.gradle
update your build.gradle as follow and refresh the project, gradle will download the required dependencies(jar files).
buildscript {
    ext {
        springBootVersion = '1.5.7.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
}

Source code (HelloWorldController.java)
Spring follows Model-View-Controller so we'll use widely used package structure and filenames. Now create package like com.example.package.rest.controller
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 */
@RestController
@RequestMapping("/api") 
public class HelloWorldController {
 
 /**
  * This will send Hello World in response with HTTP status code 200.
  * @return
  */
 @GetMapping("/helloworld")
 public ResponseEntity sayHello(){
  /* return response */
  return ResponseEntity.ok("Hello World");
 }
}
Lets quickly understand annotations used in HelloWorldController.java

  • @RestController - To denote class will be service as rest service
  • @RequestMapping("/api") - default/root path for all apis created under HelloWorldController.java
  • @GetMapping("/helloworld") - sayHello method will serve for GET http request made for path /api/helloworld

Source code (Application.java)
Create Application.java (main-class) in package best suited your project,
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 */
@SpringBootApplication
@ComponentScan(basePackages = { "com.javaquery.examples.springboot.rest" })
public class Application {
 public static void main(String[] args) {
  SpringApplication.run(Application.class, args);
 }
}
Lets quickly understand annotations used in Application.java

  • @SpringBootApplication - denotes entry point of spring-boot application
  • @ComponentScan - files under given package(s) are part/component of spring application so spring will process the annotations.
Now run Application.java like any other java application with main method. It'll start local server and your rest API is ready to test. Open up localhost:8080/api/helloworld in your browser.

Project Structure

Source code is also available on my github repository: https://github.com/javaquery/spring-boot-examples. You can also check spring tutorials on http://www.javaquery.com/p/spring-tutorials.html and new you can do Passing and validating RequestParam in spring-boot rest api