Google Cloud Spanner + Spring Boot - Build REST CRUD APIs
In this section, we will learn how to build REST CRUD APIs with Spring Boot, and Google Cloud Spanner.
1. A little bit of Background
Google Cloud Spanner
Spring Boot
2. Create a GCP Project
First, Sign into the Google console at https://console.cloud.google.com.
You can create a new project by first selecting the project dropdown in the top left and selecting "New Project".
Next, specify your GCP Project name and Project ID.
Then Click on the "CREATE" button.
Copy "Project ID" and keep it for future purposes.
3. Initialize Cloud Spanner
From cloud console, search for "Spanner" like below and click on "Spanner" button.
Next, click on "START A FREE TRIAL" button,
After that, create your free trial instance, enter "Instance name", "Instance ID", and "Region", and then click on "CREATE FREE TRIAL INSTANCE".Copy "Instance ID" and keep it for future purposes.
Next, create a database within the instance.
Click on "CREATE DATABASE" button.
Then, enter "Database name", and choose "Google Standard SQL", and then click on "CREATE" button.
From cloud console, search for "Spanner" like below and click on "Spanner" button.
Next, click on "START A FREE TRIAL" button,
After that, create your free trial instance, enter "Instance name", "Instance ID", and "Region", and then click on "CREATE FREE TRIAL INSTANCE".Copy "Instance ID" and keep it for future purposes.
Next, create a database within the instance.
Click on "CREATE DATABASE" button.
Then, enter "Database name", and choose "Google Standard SQL", and then click on "CREATE" button.
4. Create a service account key
First choose "IAM & Admin" and then click on "Service accounts".
After that, click on "CREATE SERVICE ACCOUNT".
Then, enter service account details like "Service account name", and "Service account ID" and click on "CREATE AND CONTINUE".
Then, grant basic role Editor.
Finally click on "DONE" button.
Then, from "Actions" click on "Manage keys".
Then, click on "Create new key".
Then, choose "Key type" as JSON and click on "CREATE" button.
Service account keys in JSON format will be download. Keep the file safe for future purposes.
5. Creating a simple spring boot web application
First, open the Spring initializr https://start.spring.io/
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-boot-gcp-spanner-crud. Here I selected the Maven project - language Java 17 - Spring Boot 3.0.3 and add Spring web dependency and GCP Support.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-boot-gcp-spanner-crud) file and downloads the project. Then, Extract the Zip file.
Then, import the project on your favourite IDE.
Final Project directory:
Note: Place service account key JSON file in resources folder (Not recommended in production environment).
In the pom.xml, add the Spring Data Cloud Spanner Spring Boot starter dependency:
In the pom.xml, add the Spring Data Cloud Spanner Spring Boot starter dependency:
<!-- Add GCP Spanner Starter -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
</dependency>
<!-- Add GCP Spanner Starter -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
</dependency>
Complete 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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-boot-gcp-spanner-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-gcp-spanner-crud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud-gcp.version>4.1.0</spring-cloud-gcp.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter</artifactId>
</dependency>
<!-- Add GCP Spanner Starter -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-dependencies</artifactId>
<version>${spring-cloud-gcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<?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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-boot-gcp-spanner-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-gcp-spanner-crud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud-gcp.version>4.1.0</spring-cloud-gcp.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter</artifactId>
</dependency>
<!-- Add GCP Spanner Starter -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-dependencies</artifactId>
<version>${spring-cloud-gcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
Specify datastore project id, datastore namespace, credential's location, instance id, and database name to application.properties file.
Specify datastore project id, datastore namespace, credential's location, instance id, and database name to application.properties file.
spring.cloud.gcp.spanner.project-id=knf-gcp-demo-project
spring.cloud.gcp.credentials.location=classpath:knf-gcp-demo-project-2e3b862b8c62.json
spring.cloud.gcp.spanner.instance-id=knf-demo-instance
spring.cloud.gcp.spanner.database=demo-db
Note: In this example, we placed service account key JSON file in resources folder (Not recommended in production environment).
More secure way is place JSON in somewhere in server or docker image, then create environment variable "GOOGLE_APPLICATION_CREDENTIALS" and give the location to your JSON FILE.
If your application is running on Google App Engine or Google Compute Engine, in most cases you should omit the "spring.cloud.gcp.credentials.location" property and instead, let Spring Cloud GCP Core Starter find the correct credentials for those environments.
spring.cloud.gcp.spanner.project-id=knf-gcp-demo-project
spring.cloud.gcp.credentials.location=classpath:knf-gcp-demo-project-2e3b862b8c62.json
spring.cloud.gcp.spanner.instance-id=knf-demo-instance
spring.cloud.gcp.spanner.database=demo-db
Create User Entity
package com.knf.dev.demo.entity;
import com.google.cloud.spring.data.spanner.core.mapping.Column;
import com.google.cloud.spring.data.spanner.core.mapping.PrimaryKey;
import com.google.cloud.spring.data.spanner.core.mapping.Table;
@Table(name="users")
public class User {
@PrimaryKey
@Column(name="user_id")
String id;
String name;
String email;
String country;
public User(String id, String name, String email, String country) {
this.id = id;
this.name = name;
this.email = email;
this.country = country;
}
public User() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
- @Table: The table that the entity models. Each instance of the entity represents a record in the table.
- @PrimaryKey: Indicates that the property is a component of the primary key.
- @Column: Optional. Maps the property to a specific column in the Spanner table, overriding the naming strategy that automatically maps the names.
package com.knf.dev.demo.entity;
import com.google.cloud.spring.data.spanner.core.mapping.Column;
import com.google.cloud.spring.data.spanner.core.mapping.PrimaryKey;
import com.google.cloud.spring.data.spanner.core.mapping.Table;
@Table(name="users")
public class User {
@PrimaryKey
@Column(name="user_id")
String id;
String name;
String email;
String country;
public User(String id, String name, String email, String country) {
this.id = id;
this.name = name;
this.email = email;
this.country = country;
}
public User() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
- @Table: The table that the entity models. Each instance of the entity represents a record in the table.
- @PrimaryKey: Indicates that the property is a component of the primary key.
- @Column: Optional. Maps the property to a specific column in the Spanner table, overriding the naming strategy that automatically maps the names.
Create User Repository
package com.knf.dev.demo.repository;
import com.google.cloud.spring.data.spanner.repository.SpannerRepository;
import com.google.cloud.spring.data.spanner.repository.query.Query;
import com.knf.dev.demo.entity.User;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends SpannerRepository<User, String> {
List<User> findByCountry(String country);
@Query("SELECT * FROM users WHERE name = @name")
List<User> findByName(@Param("name") String userName);
}
The interface extends SpannerRepository<User, String> where User is the domain class and String is the Id type. We declare two query methods in our repository for which implementations are generated automatically behind the scenes.
package com.knf.dev.demo.repository;
import com.google.cloud.spring.data.spanner.repository.SpannerRepository;
import com.google.cloud.spring.data.spanner.repository.query.Query;
import com.knf.dev.demo.entity.User;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends SpannerRepository<User, String> {
List<User> findByCountry(String country);
@Query("SELECT * FROM users WHERE name = @name")
List<User> findByName(@Param("name") String userName);
}
The interface extends SpannerRepository<User, String> where User is the domain class and String is the Id type. We declare two query methods in our repository for which implementations are generated automatically behind the scenes.
-Exception handling
Create ResourceNotFoundException
package com.knf.dev.demo.exception;
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
package com.knf.dev.demo.exception;
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
Create GlobalExceptionHandler
Spring supports exception handling by a Global Exception Handler (@ExceptionHandler) with Controller Advice (@ControllerAdvice). This enables a mechanism that makes ResponseEntity work with the type safety and flexibility of @ExceptionHandler.package com.knf.dev.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<CustomErrorResponse>
globalExceptionHandler(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
}
package com.knf.dev.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<CustomErrorResponse>
globalExceptionHandler(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
}
Create CustomErrorResponse
package com.knf.dev.demo.exception;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class CustomErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private int status;
private String error;
public LocalDateTime getTimestamp()
{
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp)
{
this.timestamp = timestamp;
}
public int getStatus()
{
return status;
}
public void setStatus(int status)
{
this.status = status;
}
public String getError()
{
return error;
}
public void setError(String error)
{
this.error = error;
}
}
package com.knf.dev.demo.exception;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class CustomErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private int status;
private String error;
public LocalDateTime getTimestamp()
{
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp)
{
this.timestamp = timestamp;
}
public int getStatus()
{
return status;
}
public void setStatus(int status)
{
this.status = status;
}
public String getError()
{
return error;
}
public void setError(String error)
{
this.error = error;
}
}
Create User Controller
package com.knf.dev.demo.controller;
import com.google.common.collect.Lists;
import com.knf.dev.demo.entity.User;
import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1")
public class UserController {
@Autowired
private UserRepository userRepository;
// get all users rest API
@GetMapping("/users")
public List<User> getAllUsers() {
return Lists.newArrayList(userRepository.findAll());
}
// get all users by name rest API
@GetMapping("/users/name/{name}")
public List<User> getAllUsersByName(@PathVariable String name) {
return userRepository.findByName(name);
}
// get all users by country rest API
@GetMapping("/users/country/{name}")
public List<User> getAllUsersByCountry(@PathVariable String name) {
return userRepository.findByCountry(name);
}
// create user rest API
@PostMapping("/users")
public User createUser(@RequestBody User user) {
//Random UUID
user.setId(UUID.randomUUID().toString());
return userRepository.save(user);
}
// get user by id rest api
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable String id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
return ResponseEntity.ok(user);
}
// update user rest api
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable String id,
@RequestBody User userDetails) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
user.setCountry(userDetails.getCountry());
user.setEmail(userDetails.getEmail());
user.setName(userDetails.getName());
User updatedUser = userRepository.save(user);
return ResponseEntity.ok(updatedUser);
}
// delete user rest api
@DeleteMapping("/users/{id}")
public ResponseEntity<Map<String, Boolean>> deleteUser
(@PathVariable String id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
userRepository.delete(user);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return ResponseEntity.ok(response);
}
}
- Spring @RestController annotation is used to create RESTful web services using Spring MVC. Spring RestController takes care of mapping request data to the defined request handler method. Once response body is generated from the handler method, it converts it to JSON response.
- @RequestMapping is used to map web requests onto specific handler classes and/or handler methods. @RequestMapping can be applied to the controller class as well as methods.
- We can use the @Autowired to mark a dependency which Spring is going to resolve and inject.
- @GetMapping annotation for mapping HTTP GET requests onto specific handler methods.
- @PostMapping annotation for mapping HTTP POST requests onto specific handler methods.
- @PutMapping annotation for mapping HTTP PUT requests onto specific handler methods.
- @DeleteMapping annotation for mapping HTTP DELETE requests onto specific handler methods.
- @RequestBody annotation is used to indicating a method parameter should be bind to the body of the HTTP request. Internally, this annotation uses HTTP Message converters to convert the body of HTTP requests to domain objects.
- @PathVariable annotation used on a method argument to bind it to the value of a URI template variable.
package com.knf.dev.demo.controller;
import com.google.common.collect.Lists;
import com.knf.dev.demo.entity.User;
import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1")
public class UserController {
@Autowired
private UserRepository userRepository;
// get all users rest API
@GetMapping("/users")
public List<User> getAllUsers() {
return Lists.newArrayList(userRepository.findAll());
}
// get all users by name rest API
@GetMapping("/users/name/{name}")
public List<User> getAllUsersByName(@PathVariable String name) {
return userRepository.findByName(name);
}
// get all users by country rest API
@GetMapping("/users/country/{name}")
public List<User> getAllUsersByCountry(@PathVariable String name) {
return userRepository.findByCountry(name);
}
// create user rest API
@PostMapping("/users")
public User createUser(@RequestBody User user) {
//Random UUID
user.setId(UUID.randomUUID().toString());
return userRepository.save(user);
}
// get user by id rest api
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable String id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
return ResponseEntity.ok(user);
}
// update user rest api
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable String id,
@RequestBody User userDetails) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
user.setCountry(userDetails.getCountry());
user.setEmail(userDetails.getEmail());
user.setName(userDetails.getName());
User updatedUser = userRepository.save(user);
return ResponseEntity.ok(updatedUser);
}
// delete user rest api
@DeleteMapping("/users/{id}")
public ResponseEntity<Map<String, Boolean>> deleteUser
(@PathVariable String id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
userRepository.delete(user);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return ResponseEntity.ok(response);
}
}
- Spring @RestController annotation is used to create RESTful web services using Spring MVC. Spring RestController takes care of mapping request data to the defined request handler method. Once response body is generated from the handler method, it converts it to JSON response.
- @RequestMapping is used to map web requests onto specific handler classes and/or handler methods. @RequestMapping can be applied to the controller class as well as methods.
- We can use the @Autowired to mark a dependency which Spring is going to resolve and inject.
- @GetMapping annotation for mapping HTTP GET requests onto specific handler methods.
- @PostMapping annotation for mapping HTTP POST requests onto specific handler methods.
- @PutMapping annotation for mapping HTTP PUT requests onto specific handler methods.
- @DeleteMapping annotation for mapping HTTP DELETE requests onto specific handler methods.
- @RequestBody annotation is used to indicating a method parameter should be bind to the body of the HTTP request. Internally, this annotation uses HTTP Message converters to convert the body of HTTP requests to domain objects.
- @PathVariable annotation used on a method argument to bind it to the value of a URI template variable.
Run the application - Application.java
package com.knf.dev.demo;
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);
}
}
Application is the entry point that sets up the Spring Boot application. The @SpringBootApplication annotation enables auto-configuration and component scanning.
package com.knf.dev.demo;
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);
}
}
Step1: Download or clone the source code from GitHub to a local machine - Click here!
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
OR
Run this Spring boot application from
- IntelliJ IDEA IDE by right click - Run 'Application.main()'
- Eclipse/STS - You can right click the project or the Application.java file and run as java application or Spring boot application.
Step1: Download or clone the source code from GitHub to a local machine - Click here!
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
OR
Run this Spring boot application from
- IntelliJ IDEA IDE by right click - Run 'Application.main()'
- Eclipse/STS - You can right click the project or the Application.java file and run as java application or Spring boot application.