Wednesday, August 6, 2025

[Tips] Spring Boot - Create REST APIs with WebFlux

Last updated on:

 

REST API is an often-seen buzzword; but building a REST API with Spring WebFlex is a new try for me. When I was reading WebFlux documentation, the keywords, non-blocking, reactive, caught my eyes. How to interpret them? 

First of all, asynchronous pattern came to my mind. In a React app, for example, when retrieve data from an external source, we use React.useEffect function to handle it. So, the app doesn’t get stuck and can continue other operations; once the data becomes available, get it rendered. On the server side of a servlet app, we use @Async annotation to define an asynchronous method. The purpose that asynchronous pattern serves is to process tasks separated from the main flow.

I googled what the difference is between non-blocking and asynchronous. It can be summed up like this.

How operations are carried out in a non-blocking pattern? When a request comes, an operation is initiated in response to it; but the thread doesn’t block there, instead,  immediately starts serving other requests. Once the original operation is completed, a notification is issued, which triggers the callback to continue to process the operation.

What purpose does it serve? Non-blocking pattern aims to utilize resources in a more efficient manner and scale up with flexibility. 

What scenario is it fit for? It is especially suitable for a highly concurrent environment.

What about reactive? It refers to a programming model built around events, or to make it simple, we can call it an event-driven model. Under this circumstance, we arrange operations by reacting to events, e.g., data readiness, operation completes, file I/O etc. 

This is what Spring WebFlux is built to address.

Mono and Flux, a couple of terms that we are going to meet in the example, are reactive types. They are like data types but different in some way: they emit not only items but also competes. Mono carries zero or one item, whereas, Flux encapsulates zero or many items.

The APIs we are going to build are identified by these routes.

  • “/rest/findalldpts” – Handler method: findAllDpts()

  • “/rest/finddptbyid/{department_id}” – Handler method: findById()

The source codes are available on GitHub



Prerequisite


Spring Tool Suite is required for this example. If you use Eclipse, you can go to Eclipse Marketplace to find and get it installed.

The Departments table is created beforehand in the database, Oracle XE.


SpringMVC project/schema.sql
CREATE TABLE Departments (
   Department_Id NUMBER(4)
 , Department_Name VARCHAR2(20)
 , Manager_Id NUMBER(6)
)
 NOLOGGING
 PARALLEL;

INSERT INTO Departments
WITH tabrows AS (
 SELECT 10, 'Administration', 100 FROM DUAL UNION ALL
 SELECT 20, 'Marketing', 200 FROM DUAL UNION ALL
 SELECT 30, 'Compliance', 300 FROM DUAL UNION ALL
 SELECT 40, 'Channel', 400 FROM DUAL UNION ALL
 SELECT 99, 'Dummy', null FROM DUAL
)
SELECT * FROM tabrows;


Create a Spring Boot Project


Click New button, choose Spring Starter Project, click Next.



Check Oracle Driver, Spring Data JDBC, Spring Reactive Web on the Dependencies page. 

Click Finish.



This is what the dependencies look like in pom.xml.


SpringREST project/pom.xml
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-webflux</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jdbc</artifactId>
	</dependency>	
	<dependency>
		<groupId>com.oracle.database.jdbc</groupId>
		<artifactId>ojdbc11</artifactId>
		<scope>runtime</scope>
	</dependency>


Configure a Data Source


The connection strings for Oracle are defined in the application property file.


SpringREST project/application-dev.properties
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/xepdb1
spring.datasource.username=user
spring.datasource.password=password

In this demo project, data source auto-configuration is disabled, as shown in the code block below. 


SpringREST project/SpringRestApplication.java
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class SpringRestApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringRestApplication.class, args);
	}
}

Therefore, we extract the connection strings and manually create a data source bean in the configuration file. 

Along with it, a JdbcTemplate bean and a TransactionManager bean are created as well. 


SpringREST project/PrimaryDataSourceConfig.java
@Configuration(proxyBeanMethods = false)
public class PrimaryDataSourceConfig {
			
	/*
	 * Primary data source
	 */
	@Primary
	@Bean
	public DataSource dataSource(
    		@Value("${spring.datasource.driver-class-name}") String dsDriverClassName,
    		@Value("${spring.datasource.url}") String dsURL,
    		@Value("${spring.datasource.username}") String dsUsername,
    		@Value("${spring.datasource.password}") String dsPassword
    		) {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName(dsDriverClassName);
		dataSource.setUrl(dsURL);
		dataSource.setUsername(dsUsername);
		dataSource.setPassword(dsPassword);
        
		return dataSource;
	}
	
	/*
	 * JdbcTemplate for primary data source
	 */
	@Primary
	@Bean
	public JdbcTemplate jdbcTemplate(
			DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}

	@Primary
	@Bean(name = "transactionManager")
	public PlatformTransactionManager transactionManager(
    		DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}
}


Data Class


After the data was fetched from the database, it is stored in Department POJOs which are passed to an API consumer across the app’s logical layers.  

The class is defined in the following code.


SpringREST project/DptDto.java
public class DptDto {
	private Long department_id;
	private String department_name;
	private Integer manager_id;
	/*
	 * Construtors, Getters, Setters etc.
	 */
}


Configure a Router and Handlers


This part is the implementation of the APIs. Web requests are processed by handler methods . To map the routes to the handlers, we define a router bean, shown in the follow code block. 

Handler findAllDpts() will return either a bunch of items or none.

Handler findById() will return either one item or none. Even there are more than one records sharing the same department_id, we get back the first element to API consumers. With an intention to differ it from findAllDpts(), we’d like to see how the client will deal with them. 

In case that items are not found, a ServerResponse object with not found status is returned.


SpringREST project/DptRouter.java
@Configuration(proxyBeanMethods = false)
public class DptRouter {
	
	@Bean
	public RouterFunction<ServerResponse> route() {

	    return RouterFunctions
	      .route(GET(AppConstant.REST_ROOT+AppConstant.API_DPT_FINDALL)
	      	.and(accept(MediaType.APPLICATION_JSON)), 
	      	this::findAllDpts)
	      .andRoute(GET(AppConstant.REST_ROOT+AppConstant.API_DPT_FINDBYID), 
	      	this::findById);
	  }
	
	@Autowired
	private DptService dptService;
	
	public Mono<ServerResponse> findAllDpts(ServerRequest request) {
		
		List<DptDto> listDpt = dptService.findAll();	
		if (!ObjectUtils.isEmpty(listDpt)) {
			return ServerResponse.ok()
					.contentType(MediaType.APPLICATION_JSON)
					.body(BodyInserters.fromValue(listDpt));
		} else {
			return ServerResponse.notFound()
					.build();
		}
	}

	public Mono<ServerResponse> findById(ServerRequest request){
		Map<String, String> pathVariables = request.pathVariables();
		Long department_id = Long.parseLong(pathVariables.get("department_id")); 

		List<DptDto> listDpt = dptService.findById(department_id);
		
		if (!ObjectUtils.isEmpty(listDpt)) {

			return ServerResponse.ok()
					.contentType(MediaType.APPLICATION_JSON)
					.body(Mono.just(listDpt.getFirst()), DptDto.class);
		} else {
			return ServerResponse.notFound()
					.build();
		}
	}
}

As an effort to avoid using literal strings in the methods, we use constants to form the routes. The constants are defined as follows. 


SpringREST project/AppConstant.java
public final class AppConstant {
	public static final String REST_ROOT = "/rest";
	public static final String API_DPT_FINDALL = "/findalldpts";
	public static final String API_DPT_FINDBYID = "/finddptbyid/{department_id}";
}


Data Persistence


This is a common example using Spring JDBC: at service layer, inject a DAO bean, call its methods to get data from the tables.

For more details about data access with Spring JDBC, refer to [Tips] Spring Boot – Access Data with JDBC.


Service definition:

SpringREST project/DptService.java
@Service
public class DptService{

	@Autowired
	DptDao dptDao;
	
	public List<DptDto> findAll() {
		List<DptDto> dptlist = dptDao.findAll();		
		return dptlist;
	}
	
	public List<DptDto> findById(Long department_id){
		return dptDao.findById(department_id);
	}


DAO definition: 

SpringREST project/DptDaoImpl.java
@Repository
public class DptDaoImpl implements DptDao {

	@Autowired
	JdbcTemplate jdbcTemplate;

	@Override
	public List<DptDto> findAll(){	
		String sql = "SELECT department_id, department_name, manager_id "
			+ "FROM departments "
			+ "ORDER BY department_id";
	    	
		List<DptDto> dptlist = null;
		try {
			dptlist = jdbcTemplate.query(sql,
				(rs, rowNum) -> new DptDto(rs.getLong("department_id"), 
					rs.getString("department_name"), 
					rs.getInt("manager_id"))
			);
		} catch (Exception e) {
			System.out.println(e);
		}		
		return dptlist;
	}

	public List<DptDto> findById(Long department_id){

		String sql = "SELECT department_id, department_name, manager_id "
			+ "FROM departments "
			+ "WHERE department_id = ?";
    	
		List<DptDto> dptlist = null;
		try {
			dptlist = jdbcTemplate.query(sql, new Object[] {department_id}, 
				new int[] {Types.INTEGER}, new RowMapper<DptDto>() {
				@Override
				public DptDto mapRow(ResultSet rs, int rowNum) 
					throws SQLException {
					DptDto dptdto = new DptDto();
					dptdto.setDepartment_id(
						rs.getLong("department_id"));
					dptdto.setDepartment_name(
						rs.getString("department_name"));
					dptdto.setManager_id(
						rs.getInt("manager_id"));
					return dptdto;
				}
			});        	
		} catch (Exception e) {
			System.out.println(e);
		}
        
		return dptlist;
	}
}	


Test the APIs


Click Run button in Eclipse to get the app running. 

Open a browser, enter the URIs. You’ll see the responses from the endpoint. 

This is what the results look like. 


Result of “/rest/findalldpts”


Result of “/rest/finddptbyid/30”



Recap


We touched on Spring WebFlux, which features in non-blocking and reactive. 

Using it, we also built APIs that return a Mono and a Flux, respectively. How does the client handle them differently? Refer to [Tips] Spring Boot - Build a Web Client Consuming REST APIs

Additionally, no item being found is a case we need to consider when design a server-side app.



Reference


Building a Reactive RESTful Web Service 

Spring WebFlux 


No comments:

Post a Comment

[Tips] Spring Boot - Check Out Spring MVC through Implementing a Simple Function

Last updated on:   When it comes to Spring MVC, the well-known MVC architecture diagram comes to our mind. It helps us underst...