---
title: "Using Spring Webflux for REST APIs: Annotated and Functional Endpoints"
description: Want to create non-blocking code with fewer hardware resources and threads? Here's how annotated and functional endpoints work in Spring Webflux.
image: https://blog.avenuecode.com/hubfs/network-g4bfcd1764_1280.jpg
---

[AvenueCode.com](https://avenuecode.com) [News](https://avenuecode.com/news) [Contact](https://avenuecode.com/contact)

[![Avenue Code Snippets Logo](https://blog.avenuecode.com/hubfs/Avenue%20Code%20New%20Logos%20-%202023/AC-Snippets---Black.png)](https://blog.avenuecode.com/?hsLang=en-us) *menu*

- Technology
  
  [Cloud](https://blog.avenuecode.com/blog/topic/cloud?hsLang=en-us) [Delivery Infrastructure](https://blog.avenuecode.com/blog/topic/delivery-infrastructure?hsLang=en-us) [Web Experience](https://blog.avenuecode.com/blog/topic/web-experience?hsLang=en-us) [Agile Mindset](https://blog.avenuecode.com/blog/topic/agile-mindset?hsLang=en-us) [Quality First](https://blog.avenuecode.com/blog/topic/quality-first?hsLang=en-us)
  
  [Design](https://blog.avenuecode.com/blog/topic/design?hsLang=en-us) [Solution Architecture](https://blog.avenuecode.com/blog/topic/solution-architecture?hsLang=en-us) [Data & ML](https://blog.avenuecode.com/blog/topic/data-and-machine-learning?hsLang=en-us) [Mobile Experience](https://blog.avenuecode.com/blog/topic/mobile-experience?hsLang=en-us)
- [Whitepapers](https://blog.avenuecode.com/blog/topic/whitepapers?hsLang=en-us)
- [Spotlight](https://blog.avenuecode.com/blog/topic/spotlight?hsLang=en-us)
- [Extraordinary Women in Tech](https://blog.avenuecode.com/blog/topic/extraordinary-women-in-tech?hsLang=en-us)
- [Avenue Code Culture](https://blog.avenuecode.com/blog/topic/avenue-code-culture?hsLang=en-us)

- [Cloud](https://blog.avenuecode.com/blog/topic/cloud?hsLang=en-us)
- [Design](https://blog.avenuecode.com/blog/topic/design?hsLang=en-us)
- [Delivery Infrastructure](https://blog.avenuecode.com/blog/topic/delivery-infrastructure?hsLang=en-us)
- [Solution Architecture](https://blog.avenuecode.com/blog/topic/solution-architecture?hsLang=en-us)
- [Web Experience](https://blog.avenuecode.com/blog/topic/web-experience?hsLang=en-us)
- [Data & ML](https://blog.avenuecode.com/blog/topic/data-and-machine-learning?hsLang=en-us)
- [Agile Mindset](https://blog.avenuecode.com/blog/topic/agile-mindset?hsLang=en-us)
- [Mobile Experience](https://blog.avenuecode.com/blog/topic/mobile-experience?hsLang=en-us)
- [Quality First](https://blog.avenuecode.com/blog/topic/quality-first?hsLang=en-us)
- [Whitepapers](https://blog.avenuecode.com/blog/topic/whitepapers?hsLang=en-us)
- [Spotlight](https://blog.avenuecode.com/blog/topic/spotlight?hsLang=en-us)
- [Extraordinary Women in Tech](https://blog.avenuecode.com/blog/topic/extraordinary-women-in-tech?hsLang=en-us)
- [Avenue Code Culture](https://blog.avenuecode.com/blog/topic/avenue-code-culture?hsLang=en-us)

# [Using Spring Webflux for REST APIs: Annotated and Functional Endpoints](https://blog.avenuecode.com/using-spring-webflux-for-rest-apis-annotated-and-functional-endpoints)

- [Tweet](https://twitter.com/share)

*perm\_identity* [Diego Zanivan](https://blog.avenuecode.com/using-spring-webflux-for-rest-apis-annotated-and-functional-endpoints/author/diego-zanivan?hsLang=en-us)

*schedule* 3/8/23 2:00 PM

Spring Webflux is the reactive stack of the Spring framework, and it enables the creation of non-blocking code using fewer hardware resources and threads. You can still create endpoints using annotations or the most recent functional method. In this blog, we will create endpoints using the functional style and the annotated style with Spring Webflux.

##### Prerequisites

Before we get started, you'll need to add the following Spring dependencies to the project:

1. Spring Reactive Web - Webflux
2. Lombok to create the boilerplate annotation
3. Optional: MongoDB driver - I used MongoDB on this project

##### Annotated Endpoints

Annotated endpoints are intuitive, easier to learn, and frequently used by many companies since they are common in older Spring versions.

This is an example of how we can create an annotated controller:

| @RestController @RequestMapping("v1/activity") public class ActivityV1Controller {   @Autowired   private ActivityService service;   @PostMapping   public Mono<Activity> save(@RequestBody Activity activity) {       return service.save(activity);   }   @GetMapping("{id}")   public Mono<Activity> getById(@PathVariable("id") String id) {       return service.findById(id);   }   @GetMapping   public Flux<Activity> getAll() {       return service.findAll();   } } |
| --- |

The @RestController annotation is used to identify this class as an endpoint, and we have defined the URI path using @RequestMapping.

@PostMapping and @GetMapping are used to receive requests using POST verb and GET verb respectively. I've injected the ActivityService with @Autowired, and I'm using the Activity class in this controller, but don't worry about this for your own project.

This is basically an annotated endpoint. Pretty simple, isn't it?

##### Functional Endpoint

Now let's create an endpoint with the same behavior but using a functional endpoint.

First things first: functional endpoints are usually divided into a handler and a router. You don't absolutely need a handler, but without it your code will probably be a mess.

Handler

Let's start by creating the handler. It will be responsible for actions like saving or retrieving data.

| @Component public class ActivityHandler {   @Autowired   private ActivityService service;   private ActivityConverter converter;   public ActivityHandler() {       converter = new ActivityConverter();   }   public Mono<ServerResponse> findAll(ServerRequest request) {       return ok().contentType(MediaType.APPLICATION\_JSON)               .body(service.findAll()                       .flatMap(x -> converter.convertToDto(x)), ActivityDto.class);   }   public Mono<ServerResponse> findById(ServerRequest request) {       return ok().contentType(MediaType.APPLICATION\_JSON)               .body(service.findById(request.pathVariable("id"))                       .flatMap(x -> converter.convertToDto(x)), ActivityDto.class);   }   public Mono<ServerResponse> save(ServerRequest request) {       return ok().contentType(MediaType.APPLICATION\_JSON)               .body(request.bodyToMono(ActivityDto.class)                       .flatMap(dto -> converter.convertToDocument(service, dto))                       .flatMap(doc -> service.save(doc))                       .doOnNext(doc -> converter.convertToDto(doc)), ActivityDto.class);   } } |
| --- |

I added the @Component annotation so that I can inject this class in my router.

I'm no longer using the Activity class, but I am using a DTO (data transfer object) representation instead. You don't need to worry about this, however, because it's totally optional, and I used it merely for the context of my own project.

The important thing to pay attention to is how we handle the incoming 'id' as a path parameter in the URL: localhost:8080/v2/activity/1, where 1 is the path parameter 'id'.

We can retrieve this id using the *ServerRequest* class and the method *pathVariable(VARIABLE\_NAME)*.

There's another difference we need to point out: When getting the request body, we used the *ServerRequest* again, but this time with the method *bodyToMono(ClassToMap.class)*. Since we are receiving a DTO, we must convert it to the actual Activity.class. It would be easier if we had used the Activity.class instead.

Router

The router is responsible for mapping the URIs and executing actions when hitting them.

| @Configuration(proxyBeanMethods = false) public class ActivityRouter {   @Bean   public RouterFunction<ServerResponse> route(ActivityHandler activityHandler) {       return RouterFunctions.route()               .path("/v2/activity", builder -> builder                       .GET("{id}", accept(MediaType.APPLICATION\_JSON), activityHandler::findById)                       .GET(accept(MediaType.APPLICATION\_JSON), activityHandler::findAll)                       .POST(accept(MediaType.APPLICATION\_JSON), activityHandler::save)                       .PUT(accept(MediaType.APPLICATION\_JSON), activityHandler::save)               )               .build()               .andRoute(GET("other"), req -> ServerResponse.ok()                       .body(Mono.just("Other route"), String.class));   } } |
| --- |

There are different ways to create routes, but I personally like this one.

We have the @Configuration Spring annotation and the method annotated with @Bean that should be processed by the Spring container. This is necessary for Spring to properly map those URIs.

We have the route method that will receive our ActivityHandler as a parameter.

The method starts by defining the path '/v2/activity' for the URI since '/v1/activity' is already defined on the annotated controller.

The URI was grouped by the path; each verb (GET, POST, PUT) will be handled differently.

Finally, I've created the mock route 'other' just to show how we can add more routes and perform some actions without a handler.

Global Handler

Now we have our endpoints in place and everything is going fine. But we are the IT crew, and we know that things don't go well all the time. What if we throw some exception and we want a default return for this exception? This is where a global handler comes in handy!

We want to respond with a 400 Bad Request status whenever an exception is thrown.

Let's create a custom exception:

| public class EntryNotFoundException extends RuntimeException {   public EntryNotFoundException(String id) {       super("No entry found for id: " + id);   } } |
| --- |

No big deal, right?

Let's now capture the error and map the return according to the exception.

There are many ways to do this, but an easy one that works for both functional and annotated endpoints is implementing the WebExceptionHandler interface.

| @Component @Order(-2) public class RestWebExceptionHandler implements WebExceptionHandler {   @Override   public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {       if (ex instanceof EntryNotFoundException) {           exchange.getResponse().setStatusCode(HttpStatus.NOT\_FOUND);       }       if (ex instanceof InvalidEntryException) {           exchange.getResponse().setStatusCode(HttpStatus.BAD\_REQUEST);       }       if (Strings.isNotBlank(ex.getMessage())) {           byte\[\] bytes = ex.getMessage().getBytes(StandardCharsets.UTF\_8);           DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);           return exchange.getResponse().writeWith(Flux.just(buffer));       }       return Mono.error(ex);   } } |
| --- |

We need to annotate it with @Order(-2) to run our class before the default handler.

I'm not doing much, just changing the response status code according to the exception and merging the exception message with the response buffer before sending it back to the user.

Now you can throw the EntryNotFoundException at any point of your code and you'll notice that our RestWebExceptionHandler will come into action.

##### Conclusion

Which style did you prefer: annotated or functional? Let me know in the comments!

Please note that there are other ways to globally handle exceptions. I recommend researching them and choosing the best fit for your project.

All of this code is available on my [Github](https://github.com/DiegoZanivan/Activity).

 

Want to learn more about Spring WebFlux? Check out our quick start guide for building reactive REST APIs with Spring:

[![Read Now](https://no-cache.hubspot.com/cta/default/2564010/086d83fc-8d8c-414e-a6a2-2cc52248cf7f.png)](https://cta-redirect.hubspot.com/cta/redirect/2564010/086d83fc-8d8c-414e-a6a2-2cc52248cf7f)

---

### Author

# Diego Zanivan

 Diego Zanivan is a Back-End Developer at Avenue Code. He's a cutting-edge technology enthusiast who's been passionate about developing awesome tools for over 15 years. Diego usually works with PHP and Java but also fills in as the Front-End guy sometimes.

---

### Related Posts

### Building Accessible Web Applications

[READ MORE](https://blog.avenuecode.com/building-accessible-web-applications?hsLang=en-us)

### How the Mulesoft JWT Validation Policy Works

[READ MORE](https://blog.avenuecode.com/mulesoft-jwt-validation-policy-works?hsLang=en-us)

### How to Use Redis Cache to Prevent DDoS Attacks

[READ MORE](https://blog.avenuecode.com/use-redis-cache-to-prevent-attacks?hsLang=en-us)

### Leave a Comment!

### Avenue Code Social

[![Facebook Icon](https://blog.avenuecode.com/hubfs/Images/Blog/facebook.png?t=1486470796564)](https://www.facebook.com/avenuecode)

[![Twitter Icon](https://blog.avenuecode.com/hubfs/Images/Blog/twitter.png?t=1486470796842)](https://twitter.com/AvenueCode)

[![LinkedIn Icon](https://blog.avenuecode.com/hubfs/Images/Blog/linkedin.png?t=1486470796556)](https://www.linkedin.com/company/avenuecode/)

### Newsletter

Want to stay on top of all tips and news from Avenue Code?

### Popular Snippets

![Avenue Code-primary versions_logo white avenue code endorsement 2](https://blog.avenuecode.com/hs-fs/hubfs/Avenue%20Code%20New%20Logos%20-%202023/Avenue%20Code-primary%20versions_logo%20white%20avenue%20code%20endorsement%202.png?width=1180&name=Avenue%20Code-primary%20versions_logo%20white%20avenue%20code%20endorsement%202.png "Avenue Code-primary versions_logo white avenue code endorsement 2")

### About Us

- [Who We Are](https://www.avenuecode.com/who-we-are)
- [What We Do](https://www.avenuecode.com/what-we-do)
- [Portfolio](https://www.avenuecode.com/portfolio)
- [Partners](https://www.avenuecode.com/partners)
- [News](https://www.avenuecode.com/news)
- [Events](https://www.avenuecode.com/events)
- [Blog](https://blog.avenuecode.com/)
- [Contact](https://www.avenuecode.com/contact)

### Our Offices

San Francisco

[+1 415 766 4178](tel:+553125161448) [ac.inquiries@avenuecode.com](mailto:brazil.info@avenuecode.com)

Belo Horizonte

[+55 31 2516 1448](tel:+553125161448) [brazil.info@avenuecode.com](mailto:brazil.info@avenuecode.com)

São Paulo

[+55 11 3205 3232](tel:+553125161448) [brazil.info@avenuecode.com](mailto:brazil.info@avenuecode.com)

### We're Hiring!

- [Belo Horizonte](https://www.avenuecode.com/who-we-are)
- [New York](https://www.avenuecode.com/what-we-do)
- [San Francisco](https://www.avenuecode.com/portfolio)
- [São Paulo](https://www.avenuecode.com/partners)

---

©2015 - 2017 Avenue Code

[![Facebook Icon](https://blog.avenuecode.com/hubfs/Images/Icons/facebook-2.png)](https://www.facebook.com/avenuecode) [![Twitter Icon](https://blog.avenuecode.com/hubfs/Images/Icons/twitter-2.png)](https://twitter.com/AvenueCode) [![LinkedIn Icon](https://blog.avenuecode.com/hubfs/Images/Icons/linkedin-2.png)](https://www.linkedin.com/company/avenue-code) [![Glassdoor Icon](https://blog.avenuecode.com/hubfs/Images/Icons/glassdoor-icon-1.png)](https://www.glassdoor.com/Overview/Working-at-Avenue-Code-EI_IE456173.11,22.htm) [![YouTube Icon](https://blog.avenuecode.com/hubfs/Images/Icons/youtube-2.png)](https://www.youtube.com/user/AvenueCodePlay)

Please enable JavaScript to view the [comments powered by Disqus.](http://disqus.com/?ref_noscript)

© 2026 Avenue Code

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Diego Zanivan",
    "url" : "https://blog.avenuecode.com/author/diego-zanivan"
  },
  "dateModified" : "2023-03-08T17:00:00.523Z",
  "datePublished" : "2023-03-08T17:00:00.000Z",
  "headline" : "Using Spring Webflux for REST APIs: Annotated and Functional Endpoints",
  "image" : [ "https://blog.avenuecode.com/hubfs/network-g4bfcd1764_1280.jpg" ],
  "mainEntityOfPage" : {
    "@id" : "https://blog.avenuecode.com/using-spring-webflux-for-rest-apis-annotated-and-functional-endpoints",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://blog.avenuecode.com/hubfs/Avenue%20Code%20New%20Logos%20-%202023/Avenue%20Code-primary%20versions_LOGO%20HORIZONTAL%20group%201-7.png"
    },
    "name" : "Avenue Code"
  }
}
```