{"id":74927,"date":"2018-03-16T19:00:19","date_gmt":"2018-03-16T17:00:19","guid":{"rendered":"https:\/\/www.javacodegeeks.com\/?p=74927"},"modified":"2018-03-16T10:55:59","modified_gmt":"2018-03-16T08:55:59","slug":"doing-stuff-with-spring-webflux","status":"publish","type":"post","link":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html","title":{"rendered":"Doing stuff with Spring WebFlux"},"content":{"rendered":"<p>Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more mentions of Spring WebFlux along with tutorials on how to use it. But after reading through them and trying to get it working myself, I found it a bit hard to make the jump from the code included in the posts and tutorials I read to writing code that actually does something a tiny bit more interesting than returning a string from the back-end. Now, I\u2019m hoping I\u2019m not shooting myself in the foot by saying that as you could probably make the same criticism of the code I use in this post, but here is my attempt to give a tutorial of Spring WebFlux that actually resembles something that you might use in the wild.<\/p>\n<p>Before I continue, and after all this mentioning of WebFlux, what actually is it? Spring WebFlux is a fully non-blocking reactive alternative to Spring MVC. It allows better vertical scaling without increasing your hardware resources. Being reactive it now makes use of Reactive Streams to allow asynchronous processing of data returned from calls to the server. This means we are going to see a lot less <code>List<\/code>s, <code>Collection<\/code>s or even single objects and instead their reactive equivalents such as <code>Flux<\/code> and <code>Mono<\/code> (from Reactor). I\u2019m not going to go to in depth on what Reactive Streams are, as honestly I need to look into it even more myself before I try to explain it to anyone. Instead lets get back to focusing on WebFlux.<\/p>\n<p>I used Spring Boot to write the code in this tutorial as usual.<\/p>\n<p>Below are the dependencies that I used in this post.<\/p>\n<pre class=\"brush:xml\">&lt;dependencies&gt;\r\n\r\n  &lt;dependency&gt;\r\n    &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\r\n    &lt;artifactId&gt;spring-boot-starter-webflux&lt;\/artifactId&gt;\r\n  &lt;\/dependency&gt;\r\n\r\n  &lt;dependency&gt;\r\n    &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\r\n    &lt;artifactId&gt;spring-boot-starter-data-cassandra-reactive&lt;\/artifactId&gt;\r\n    &lt;version&gt;2.0.0.RELEASE&lt;\/version&gt;\r\n  &lt;\/dependency&gt;\r\n\r\n&lt;\/dependencies&gt;<\/pre>\n<p>Although I didn\u2019t include it in the dependency snippet above, the <code>spring-boot-starter-parent<\/code> is used, which can finally be upped to version <code>2.0.0.RELEASE<\/code>. Being this tutorial is about WebFlux, including the <code>spring-boot-starter-webflux<\/code> is obviously a good idea. <code>spring-boot-starter-data-cassandra-reactive<\/code> has also been included as we will be using this as the database for the example application as it is one of the few databases that have reactive support (at the time of writing). By using these dependencies together our application can be fully reactive from front to back.<\/p>\n<p>WebFlux introduces a different way to handle requests instead of using the <code>@Controller<\/code> or <code>@RestController<\/code> programming model that is used in Spring MVC. But, it does not replace it. Instead it has been updated to allow reactive types to be used. This allows you to keep the same format that you are used to writing with Spring but with a few changes to the return types so <code>Flux<\/code>s or <code>Mono<\/code>s are returned instead. Below is a very contrived example.<\/p>\n<pre class=\"brush:java\">@RestController\r\npublic class PersonController {\r\n\r\n  private final PersonRepository personRepository;\r\n\r\n  public PersonController(PersonRepository personRepository) {\r\n    this.personRepository = personRepository;\r\n  }\r\n\r\n  @GetMapping(\"\/people\")\r\n  public Flux&lt;Person&gt; all() {\r\n    return personRepository.findAll();\r\n  }\r\n\r\n  @GetMapping(\"\/people\/{id}\")\r\n\tMono&lt;Person&gt; findById(@PathVariable String id) {\r\n\t\treturn personRepository.findOne(id);\r\n\t}\r\n}<\/pre>\n<p>To me this looks very familiar and from a quick glance it doesn\u2019t really look any different from your standard Spring MVC controller, but after reading through the methods we can see the different return types from what we would normally expect. In this example <code>PersonRepository<\/code> must be a reactive repository as we have been able to directly return the results of their search queries, for reference, reactive repositories will return a <code>Flux<\/code> for collections and a <code>Mono<\/code> for singular entities.<\/p>\n<p>The annotation method is not what I want to focus on in this post though. It\u2019s not cool and hip enough for us. There isn\u2019t enough use of lambdas to satisfy our thirst for writing Java in a more functional way. But Spring WebFlux has our backs. It provides an alternative method to route and handle requests to our servers that lightly uses lambdas to write router functions. Let\u2019s take a look at an example.<\/p>\n<pre class=\"brush:java; wrap-lines:false\">@Configuration\r\npublic class PersonRouter {\r\n\r\n  @Bean\r\n  public RouterFunction&lt;ServerResponse&gt; route(PersonHandler personHandler) {\r\n    return RouterFunctions.route(GET(\"\/people\/{id}\").and(accept(APPLICATION_JSON)), personHandler::get)\r\n        .andRoute(GET(\"\/people\").and(accept(APPLICATION_JSON)), personHandler::all)\r\n        .andRoute(POST(\"\/people\").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post)\r\n        .andRoute(PUT(\"\/people\/{id}\").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put)\r\n        .andRoute(DELETE(\"\/people\/{id}\"), personHandler::delete)\r\n        .andRoute(GET(\"\/people\/country\/{country}\").and(accept(APPLICATION_JSON)), personHandler::getByCountry);\r\n  }\r\n}<\/pre>\n<p>These are all the routes to methods in the <code>PersonHandler<\/code> which we will look at later on. We have created a bean that will handle our routing. To setup the routing functions we use the well named <code>RouterFunctions<\/code> class providing us with a load of static methods, but for now we are only interested with it\u2019s <code>route<\/code> method. Below is the signature of the <code>route<\/code> method.<\/p>\n<pre class=\"brush:java\">public static &lt;T extends ServerResponse&gt; RouterFunction&lt;T&gt; route(\r\n      RequestPredicate predicate, HandlerFunction&lt;T&gt; handlerFunction) {\r\n  \/\/ stuff\r\n}<\/pre>\n<p>The method shows that it takes in a <code>RequestPredicate<\/code> along with a <code>HandlerFunction<\/code> and outputs a <code>RouterFunction<\/code>.<\/p>\n<p>The <code>RequestPredicate<\/code> is what we use to specify behavior of the route, such as the path to our handler function, what type of request it is and the type of input it can accept. Due to my use of static imports to make everything read a bit clearer, some important information has been hidden from you. To create a <code>RequestPredicate<\/code> we should use the <code>RequestPredicates<\/code> (plural), a static helper class providing us with all the methods we need. Personally I do recommend statically importing <code>RequestPredicates<\/code> otherwise you code will be a mess due to the amount of times you might need to make use of <code>RequestPredicates<\/code> static methods. In the above example, <code>GET<\/code>, <code>POST<\/code>, <code>PUT<\/code>, <code>DELETE<\/code>, <code>accept<\/code> and <code>contentType<\/code> are all static <code>RequestPredicates<\/code> methods.<\/p>\n<p>The next parameter is a <code>HandlerFunction<\/code>, which is a Functional Interface. There are three pieces of important information here, it has a generic type of <code>&lt;T extends ServerResponse&gt;<\/code>, it\u2019s <code>handle<\/code> method returns a <code>Mono&lt;T&gt;<\/code> and it takes in a <code>ServerRequest<\/code>. Using these we can determine that we need to pass in a function that returns a <code>Mono&lt;ServerResponse&gt;<\/code> (or one of it\u2019s subtypes). This obviously places a heavy constraint onto what is returned from our handler functions as they must meet this requirement or they will not be suitable for use in this format.<\/p>\n<p>Finally the output is a <code>RouterFunction<\/code>. This can then be returned and will be used to route to whatever function we specified. But normally we would want to route lots of different requests to various handlers at once, which WebFlux caters for. Due to <code>route<\/code> returning a <code>RouterFunction<\/code> and the fact that <code>RouterFunction<\/code> also has its own routing method available, <code>andRoute<\/code>, we can chain the calls together and keep adding all the extra routes that we require.<\/p>\n<p>If we take another look back at the <code>PersonRouter<\/code> example above, we can see that the methods are named after the REST verbs such as <code>GET<\/code> and <code>POST<\/code> that define the path and type of requests that a handler will take. If we take the first <code>GET<\/code> request for example, it is routing to <code>\/people<\/code> with a path variable name <code>id<\/code> (path variable denoted by <code>{id}<\/code>) and the type of the returned content, specifically <code>APPLICATION_JSON<\/code> (static field from <code>MediaType<\/code>) is defined using the <code>accept<\/code> method. If a different path is used, it will not be handled. If the path is correct but the Accept header is not one of the accepted types, then the request will fail.<div style=\"display:inline-block; margin: 15px 0;\"> <div id=\"adngin-JavaCodeGeeks_incontent_video-0\" style=\"display:inline-block;\"><\/div> <\/div><\/p>\n<p>Before we continue I want to go over the <code>accept<\/code> and <code>contentType<\/code> methods. Both of these set request headers, <code>accept<\/code> matches to the Accept header and <code>contentType<\/code> to Content-Type. The Accept header defines what Media Types are acceptable for the response, as we were returning JSON representations of the <code>Person<\/code> object setting it to <code>APPLICATION_JSON<\/code> (<code>application\/json<\/code> in the actual header) makes sense. The Content-Type has the same idea but instead describes what Media Type is inside the body of the sent request. That is why only the <code>POST<\/code> and <code>PUT<\/code> verbs have <code>contentType<\/code> included as the others do not have anything contained in their bodies. <code>DELETE<\/code> does not include <code>accept<\/code> and <code>contentType<\/code> so we can conclude that it is neither expecting anything to be returned nor including anything in its request body.<\/p>\n<p>Now that we know how to setup the routes, lets look at writing the handler methods that deal with the incoming requests. Below is the code that handles all the requests from the routes that were defined in the earlier example.<\/p>\n<pre class=\"brush:java\">@Component\r\npublic class PersonHandler {\r\n\r\n  private final PersonManager personManager;\r\n\r\n  public PersonHandler(PersonManager personManager) {\r\n    this.personManager = personManager;\r\n  }\r\n\r\n  public Mono&lt;ServerResponse&gt; get(ServerRequest request) {\r\n    final UUID id = UUID.fromString(request.pathVariable(\"id\"));\r\n    final Mono&lt;Person&gt; person = personManager.findById(id);\r\n    return person\r\n        .flatMap(p -&gt; ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class)))\r\n        .switchIfEmpty(notFound().build());\r\n  }\r\n\r\n  public Mono&lt;ServerResponse&gt; all(ServerRequest request) {\r\n    return ok().contentType(APPLICATION_JSON)\r\n        .body(fromPublisher(personManager.findAll(), Person.class));\r\n  }\r\n\r\n  public Mono&lt;ServerResponse&gt; put(ServerRequest request) {\r\n    final UUID id = UUID.fromString(request.pathVariable(\"id\"));\r\n    final Mono&lt;Person&gt; person = request.bodyToMono(Person.class);\r\n    return personManager\r\n        .findById(id)\r\n        .flatMap(\r\n            old -&gt;\r\n                ok().contentType(APPLICATION_JSON)\r\n                    .body(\r\n                        fromPublisher(\r\n                            person\r\n                                .map(p -&gt; new Person(p, id))\r\n                                .flatMap(p -&gt; personManager.update(old, p)),\r\n                            Person.class)))\r\n        .switchIfEmpty(notFound().build());\r\n  }\r\n\r\n  public Mono&lt;ServerResponse&gt; post(ServerRequest request) {\r\n    final Mono&lt;Person&gt; person = request.bodyToMono(Person.class);\r\n    final UUID id = UUID.randomUUID();\r\n    return created(UriComponentsBuilder.fromPath(\"people\/\" + id).build().toUri())\r\n        .contentType(APPLICATION_JSON)\r\n        .body(\r\n            fromPublisher(\r\n                person.map(p -&gt; new Person(p, id)).flatMap(personManager::save), Person.class));\r\n  }\r\n\r\n  public Mono&lt;ServerResponse&gt; delete(ServerRequest request) {\r\n    final UUID id = UUID.fromString(request.pathVariable(\"id\"));\r\n    return personManager\r\n        .findById(id)\r\n        .flatMap(p -&gt; noContent().build(personManager.delete(p)))\r\n        .switchIfEmpty(notFound().build());\r\n  }\r\n\r\n  public Mono&lt;ServerResponse&gt; getByCountry(ServerRequest serverRequest) {\r\n    final String country = serverRequest.pathVariable(\"country\");\r\n    return ok().contentType(APPLICATION_JSON)\r\n        .body(fromPublisher(personManager.findAllByCountry(country), Person.class));\r\n  }\r\n}<\/pre>\n<p>One thing that is quite noticeable, is the lack of annotations. Bar the <code>@Component<\/code> annotation to auto create a <code>PersonHandler<\/code> bean there are no other Spring annotations.<\/p>\n<p>I have tried to keep most of the repository logic out of this class and have hidden any references to the entity objects by going via the <code>PersonManager<\/code> that delegates to the <code>PersonRepository<\/code> it contains. If you are interested in the code within <code>PersonManager<\/code> then it can be seen here on my <a href=\"https:\/\/github.com\/lankydan\/spring-boot-webflux\/blob\/master\/src\/main\/java\/com\/lankydanblog\/tutorial\/person\/PersonManager.java\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>, further explanations about it will be excluded for this post so we can focus on WebFlux itself.<\/p>\n<p>Ok, back to the code at hand. Let\u2019s take a closer look at the <code>get<\/code> and <code>post<\/code> methods to figure out what is going on.<\/p>\n<pre class=\"brush:java\">public Mono&lt;ServerResponse&gt; get(ServerRequest request) {\r\n  final UUID id = UUID.fromString(request.pathVariable(\"id\"));\r\n  final Mono&lt;Person&gt; person = personManager.findById(id);\r\n  return person\r\n      .flatMap(p -&gt; ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class)))\r\n      .switchIfEmpty(notFound().build());\r\n}<\/pre>\n<p>This method is for retrieving a single record from the database that backs this example application. Due to Cassandra being the database of choice I have decided to use an <code>UUID<\/code> for the primary key of each record, this has the unfortunate effect of making testing the example more annoying but nothing that some copy and pasting can\u2019t solve.<\/p>\n<p>Remember that a path variable was included in the path for this <code>GET<\/code> request. Using the <code>pathVariable<\/code> method on the <code>ServerRequest<\/code> passed into the method we are able to extract it\u2019s value by providing the name of the variable, in this case <code>id<\/code>. The ID is then converted into a <code>UUID<\/code>, which will throw an exception if the string is not in the correct format, I decided to ignore this problem so the example code doesn\u2019t get messier.<\/p>\n<p>Once we have the ID, we can query the database for the existence of a matching record. A <code>Mono&lt;Person&gt;<\/code> is returned which either contains the existing record mapped to a <code>Person<\/code> or it left as an empty <code>Mono<\/code>.<\/p>\n<p>Using the returned <code>Mono<\/code> we can output different responses depending on it\u2019s existence. This means we can return useful status codes to the client to go along with the contents of the body. If the record exists then <code>flatMap<\/code> returns a <code>ServerResponse<\/code> with the <code>OK<\/code> status. Along with this status we want to output the record, to do this we specify the content type of the body, in this case <code>APPLICATION_JSON<\/code>, and add the record into it. <code>fromPublisher<\/code> takes our <code>Mono&lt;Person&gt;<\/code> (which is a <code>Publisher<\/code>) along with the <code>Person<\/code> class so it knows what it is mapping into the body. <code>fromPublisher<\/code> is a static method from the <code>BodyInserters<\/code> class.<\/p>\n<p>If the record does not exist, then the flow will move into the <code>switchIfEmpty<\/code> block and return a <code>NOT FOUND<\/code> status. As nothing is found, the body can be left empty so we just create the <code>ServerResponse<\/code> there are then.<\/p>\n<p>Now onto the <code>post<\/code> handler.<\/p>\n<pre class=\"brush:java\">public Mono&lt;ServerResponse&gt; post(ServerRequest request) {\r\n  final Mono&lt;Person&gt; person = request.bodyToMono(Person.class);\r\n  final UUID id = UUID.randomUUID();\r\n  return created(UriComponentsBuilder.fromPath(\"people\/\" + id).build().toUri())\r\n      .contentType(APPLICATION_JSON)\r\n      .body(\r\n          fromPublisher(\r\n              person.map(p -&gt; new Person(p, id)).flatMap(personManager::save), Person.class));\r\n}<\/pre>\n<p>Even just from the first line we can see that it is already different to how the <code>get<\/code> method was working. As this is a <code>POST<\/code> request it needs to accept the object that we want to persist from the body of the request. As we are trying to insert a single record we will use the request\u2019s <code>bodyToMono<\/code> method to retrieve the <code>Person<\/code> from the body. If you were dealing with multiple records you would probably want to use <code>bodyToFlux<\/code> instead.<\/p>\n<p>We will return a <code>CREATED<\/code> status using the <code>created<\/code> method that takes in a <code>URI<\/code> to determine the path to the inserted record. It then follows a similar setup as the <code>get<\/code> method by using the <code>fromPublisher<\/code> method to add the new record to the body of the response. The code that forms the <code>Publisher<\/code> is slightly different but the output is still a <code>Mono&lt;Person&gt;<\/code> which is what matters. Just for further explanation about how the inserting is done, the <code>Person<\/code> passed in from the request is mapped to a new <code>Person<\/code> using the <code>UUID<\/code> we generated and is then passed to <code>save<\/code> by calling <code>flatMap<\/code>. By creating a new <code>Person<\/code> we only insert values into Cassandra that we allow, in this case we do not want the <code>UUID<\/code> passed in from the request body.<\/p>\n<p>So, that\u2019s about it when it comes to the handlers. Obviously there other methods that we didn\u2019t go through. They all work differently but all follow the same concept of returning a <code>ServerResponse<\/code> that contains a suitable status code and record(s) in the body if required.<\/p>\n<p>We have now written all the code we need to get a basic Spring WebFlux back-end up a running. All that is left is to tie all the configuration together, which is easy with Spring Boot.<\/p>\n<pre class=\"brush:java\">@SpringBootApplication\r\npublic class Application {\r\n  public static void main(String args[]) {\r\n    SpringApplication.run(Application.class);\r\n  }\r\n}<\/pre>\n<p>Rather than ending the post here we should probably look into how to actually make use of the code.<\/p>\n<p>Spring provides the <code>WebClient<\/code> class to handle requests without blocking. We can make use of this now as a way to test the application, although there is also a <code>WebTestClient<\/code> which we could use here instead. The <code>WebClient<\/code> is what you would use instead of the blocking <code>RestTemplate<\/code> when creating a reactive application.<\/p>\n<p>Below is some code that calls the handlers that were defined in the <code>PersonHandler<\/code>.<\/p>\n<pre class=\"brush:java\">public class Client {\r\n\r\n  private WebClient client = WebClient.create(\"http:\/\/localhost:8080\");\r\n\r\n  public void doStuff() {\r\n\r\n    \/\/ POST\r\n    final Person record = new Person(UUID.randomUUID(), \"John\", \"Doe\", \"UK\", 50);\r\n    final Mono&lt;ClientResponse&gt; postResponse =\r\n        client\r\n            .post()\r\n            .uri(\"\/people\")\r\n            .body(Mono.just(record), Person.class)\r\n            .accept(APPLICATION_JSON)\r\n            .exchange();\r\n    postResponse\r\n        .map(ClientResponse::statusCode)\r\n        .subscribe(status -&gt; System.out.println(\"POST: \" + status.getReasonPhrase()));\r\n\r\n    \/\/ GET\r\n    client\r\n        .get()\r\n        .uri(\"\/people\/{id}\", \"a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4\")\r\n        .accept(APPLICATION_JSON)\r\n        .exchange()\r\n        .flatMap(response -&gt; response.bodyToMono(Person.class))\r\n        .subscribe(person -&gt; System.out.println(\"GET: \" + person));\r\n\r\n    \/\/ ALL\r\n    client\r\n        .get()\r\n        .uri(\"\/people\")\r\n        .accept(APPLICATION_JSON)\r\n        .exchange()\r\n        .flatMapMany(response -&gt; response.bodyToFlux(Person.class))\r\n        .subscribe(person -&gt; System.out.println(\"ALL: \" + person));\r\n\r\n    \/\/ PUT\r\n    final Person updated = new Person(UUID.randomUUID(), \"Peter\", \"Parker\", \"US\", 18);\r\n    client\r\n        .put()\r\n        .uri(\"\/people\/{id}\", \"ec2212fc-669e-42ff-9c51-69782679c9fc\")\r\n        .body(Mono.just(updated), Person.class)\r\n        .accept(APPLICATION_JSON)\r\n        .exchange()\r\n        .map(ClientResponse::statusCode)\r\n        .subscribe(response -&gt; System.out.println(\"PUT: \" + response.getReasonPhrase()));\r\n\r\n    \/\/ DELETE\r\n    client\r\n        .delete()\r\n        .uri(\"\/people\/{id}\", \"ec2212fc-669e-42ff-9c51-69782679c9fc\")\r\n        .exchange()\r\n        .map(ClientResponse::statusCode)\r\n        .subscribe(status -&gt; System.out.println(\"DELETE: \" + status));\r\n  }\r\n}<\/pre>\n<p>Don\u2019t forget to instantiate the <code>Client<\/code> somewhere, below is a nice lazy way to do it!<\/p>\n<pre class=\"brush:java\">@SpringBootApplication\r\npublic class Application {\r\n  public static void main(String args[]) {\r\n    SpringApplication.run(Application.class);\r\n    Client client = new Client();\r\n    client.doStuff();\r\n  }\r\n}<\/pre>\n<p>First we create the <code>WebClient<\/code>.<\/p>\n<pre class=\"brush:java\">private final WebClient client = WebClient.create(\"http:\/\/localhost:8080\");<\/pre>\n<p>Once created we can start doing stuff with it, hence the <code>doStuff<\/code> method.<\/p>\n<p>Let\u2019s break down the <code>POST<\/code> request that is being send to the back-end.<\/p>\n<pre class=\"brush:java\">final Mono&lt;ClientResponse&gt; postResponse =\r\n    client\r\n        .post()\r\n        .uri(\"\/people\")\r\n        .body(Mono.just(record), Person.class)\r\n        .accept(APPLICATION_JSON)\r\n        .exchange();\r\npostResponse\r\n    .map(ClientResponse::statusCode)\r\n    .subscribe(status -&gt; System.out.println(\"POST: \" + status.getReasonPhrase()));<\/pre>\n<p>I wrote this one down slightly differently so you can see that a <code>Mono&lt;ClientResponse&gt;<\/code> is returned from sending a request. The <code>exchange<\/code> method fires the HTTP request over to the server. The response will then be dealt with whenever the response arrives, if it ever does.<\/p>\n<p>Using the <code>WebClient<\/code> we specify that we want to send a <code>POST<\/code> request using the <code>post<\/code> method of course. The <code>URI<\/code> is then added with the <code>uri<\/code> method (overloaded method, this one takes in a <code>String<\/code> but another accepts a <code>URI<\/code>). Im tired of saying this method does what the method is called so, the contents of the body are then added along with the Accept header. Finally we send the request by calling <code>exchange<\/code>.<\/p>\n<p>Note that the Media Type of <code>APPLICATION_JSON<\/code> matches up with the type defined in the <code>POST<\/code> router function. If we were to send a different type, say <code>TEXT_PLAIN<\/code> we would get a <code>404<\/code> error as no handler exists that matches to what the request is expecting to be returned.<\/p>\n<p>Using the <code>Mono&lt;ClientResponse&gt;<\/code> returned by calling <code>exchange<\/code> we can map it\u2019s contents to our desired output. In the case of the example above, the status code is printed to the console. If we think back to the <code>post<\/code> method in <code>PersonHandler<\/code>, remember that it can only return the \u201cCreated\u201d status, but if the sent request does not match up correctly then \u201cNot Found\u201d will be printed out.<\/p>\n<p>Let\u2019s look at one of the other requests.<\/p>\n<pre class=\"brush:java\">client\r\n    .get()\r\n    .uri(\"\/people\/{id}\", \"a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4\")\r\n    .accept(APPLICATION_JSON)\r\n    .exchange()\r\n    .flatMap(response -&gt; response.bodyToMono(Person.class))\r\n    .subscribe(person -&gt; System.out.println(\"GET: \" + person));<\/pre>\n<p>This is our typical <code>GET<\/code> request. It looks pretty similar to the <code>POST<\/code> request we just went through. The main differences are that <code>uri<\/code> takes in both the path of the request and the <code>UUID<\/code> (as a <code>String<\/code> in this case) as a parameter to that will replace the path variable <code>{id}<\/code> and that the body is left empty. How the response is handled is also different. In this example it extracts the body of the response and maps it to a <code>Mono&lt;Person&gt;<\/code> and prints it out. This could have been done with the previous <code>POST<\/code> example but the status code of the response was more useful for it\u2019s scenario.<\/p>\n<p>For a slightly different perspective, we could use cURL to make requests and see what the response looks like.<\/p>\n<pre class=\"brush:java\">CURL -H \"Accept:application\/json\" -i localhost:8080\/people<\/pre>\n<pre class=\"brush:js\">HTTP\/1.1 200 OK\r\ntransfer-encoding: chunked\r\nContent-Type: application\/json\r\n\r\n[\r\n  {\r\n      \"id\": \"13c403a2-6770-4174-8b76-7ba7b75ef73d\",\r\n      \"firstName\": \"John\",\r\n      \"lastName\": \"Doe\",\r\n      \"country\": \"UK\",\r\n      \"age\": 50\r\n  },\r\n  {\r\n      \"id\": \"fbd53e55-7313-4759-ad74-6fc1c5df0986\",\r\n      \"firstName\": \"Peter\",\r\n      \"lastName\": \"Parker\",\r\n      \"country\": \"US\",\r\n      \"age\": 50\r\n  }\r\n]<\/pre>\n<p>The response will look something like this, obviously it will differ depending on the data you have stored.<\/p>\n<p>Note the response headers.<\/p>\n<pre class=\"brush:bash\">transfer-encoding: chunked\r\nContent-Type: application\/json<\/pre>\n<p>The <code>transfer-encoding<\/code> here represents data that is transferred in chunks that can be used to stream data. This is what we need so the client can act reactively to the data that is returned to it.<\/p>\n<p>I think that this should be a good place to stop. We have covered quite a lot of material here which has hopefully helped you understand Spring WebFlux better. There are a few other topics I want to cover about WebFlux but I will do those in separate posts as I think this one is long enough as it is.<\/p>\n<p>In conclusion, in this post we very briefly discussed why you would want to use Spring WebFlux over a typical Spring MVC back-end. We then looked at how to setup routes and handlers to process the incoming requests. The handlers implemented methods that could deal with most of the REST verbs and returned the correct data and status codes in their responses. Finally we looked at two ways to make requests to the back-end, one using a <code>WebClient<\/code> to process the output directly on the client side and another via cURL to see what the returned JSON looks like.<\/p>\n<p>If you are interested in looking at the rest of the code I used to create the example application for this post, it can be found on my <a href=\"https:\/\/github.com\/lankydan\/spring-boot-webflux\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>.<\/p>\n<p>As always if you found this post helpful, please share it and if you want to keep up with my latest posts then you can follow me on Twitter at <a href=\"https:\/\/twitter.com\/LankyDanDev\" target=\"_blank\" rel=\"noopener\">@LankyDanDev<\/a>.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td>Published on Java Code Geeks with permission by Dan Newton, partner at our <a href=\"\/\/www.javacodegeeks.com\/join-us\/jcg\/\" target=\"_blank\" rel=\"noopener\">JCG program<\/a>. See the original article here: <a href=\"https:\/\/lankydanblog.com\/2018\/03\/15\/doing-stuff-with-spring-webflux\/\" target=\"_blank\" rel=\"noopener\">Doing stuff with Spring WebFlux<\/a><\/p>\n<p>Opinions expressed by Java Code Geeks contributors are their own.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more mentions of Spring WebFlux along with tutorials on how to use it. But after reading through them and trying to get it working myself, I &hellip;<\/p>\n","protected":false},"author":12894,"featured_media":240,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[196,1714,30,854,1636,1715],"class_list":["post-74927","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-enterprise-java","tag-java-8","tag-reactive-streams","tag-spring","tag-spring-boot","tag-spring-data-cassandra","tag-spring-webflux"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Doing stuff with Spring WebFlux - Java Code Geeks<\/title>\n<meta name=\"description\" content=\"Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Doing stuff with Spring WebFlux - Java Code Geeks\" \/>\n<meta property=\"og:description\" content=\"Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html\" \/>\n<meta property=\"og:site_name\" content=\"Java Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/javacodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2018-03-16T17:00:19+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/spring-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Dan Newton\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@LankyDanDev\" \/>\n<meta name=\"twitter:site\" content=\"@javacodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Dan Newton\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"19 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html\"},\"author\":{\"name\":\"Dan Newton\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/00ad664c44d777ebfa4d984dcf2717e2\"},\"headline\":\"Doing stuff with Spring WebFlux\",\"datePublished\":\"2018-03-16T17:00:19+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html\"},\"wordCount\":2724,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/spring-logo.jpg\",\"keywords\":[\"Java 8\",\"Reactive Streams\",\"Spring\",\"Spring Boot\",\"SPRING DATA CASSANDRA\",\"Spring WebFlux\"],\"articleSection\":[\"Enterprise Java\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html\",\"name\":\"Doing stuff with Spring WebFlux - Java Code Geeks\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/spring-logo.jpg\",\"datePublished\":\"2018-03-16T17:00:19+00:00\",\"description\":\"Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#primaryimage\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/spring-logo.jpg\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2012\\\/10\\\/spring-logo.jpg\",\"width\":150,\"height\":150,\"caption\":\"spring-interview-questions-answers\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/2018\\\/03\\\/doing-stuff-with-spring-webflux.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Java\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/category\\\/java\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Enterprise Java\",\"item\":\"https:\\\/\\\/www.javacodegeeks.com\\\/category\\\/java\\\/enterprise-java\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Doing stuff with Spring WebFlux\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#website\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\",\"name\":\"Java Code Geeks\",\"description\":\"Java Developers Resource Center\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\"},\"alternateName\":\"JCG\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.javacodegeeks.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2022\\\/06\\\/exelixis-logo.png\",\"contentUrl\":\"https:\\\/\\\/www.javacodegeeks.com\\\/wp-content\\\/uploads\\\/2022\\\/06\\\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/javacodegeeks\",\"https:\\\/\\\/x.com\\\/javacodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.javacodegeeks.com\\\/#\\\/schema\\\/person\\\/00ad664c44d777ebfa4d984dcf2717e2\",\"name\":\"Dan Newton\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b5024a6405bf07e14dacc299779fc0abded33b42a05190784776429e2c76435f?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b5024a6405bf07e14dacc299779fc0abded33b42a05190784776429e2c76435f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b5024a6405bf07e14dacc299779fc0abded33b42a05190784776429e2c76435f?s=96&d=mm&r=g\",\"caption\":\"Dan Newton\"},\"sameAs\":[\"https:\\\/\\\/lankydanblog.com\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/danknewton\\\/\",\"https:\\\/\\\/x.com\\\/LankyDanDev\"],\"url\":\"https:\\\/\\\/www.javacodegeeks.com\\\/author\\\/dan-newton\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Doing stuff with Spring WebFlux - Java Code Geeks","description":"Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html","og_locale":"en_US","og_type":"article","og_title":"Doing stuff with Spring WebFlux - Java Code Geeks","og_description":"Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more","og_url":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html","og_site_name":"Java Code Geeks","article_publisher":"https:\/\/www.facebook.com\/javacodegeeks","article_published_time":"2018-03-16T17:00:19+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/spring-logo.jpg","type":"image\/jpeg"}],"author":"Dan Newton","twitter_card":"summary_large_image","twitter_creator":"@LankyDanDev","twitter_site":"@javacodegeeks","twitter_misc":{"Written by":"Dan Newton","Est. reading time":"19 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#article","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html"},"author":{"name":"Dan Newton","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/00ad664c44d777ebfa4d984dcf2717e2"},"headline":"Doing stuff with Spring WebFlux","datePublished":"2018-03-16T17:00:19+00:00","mainEntityOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html"},"wordCount":2724,"commentCount":0,"publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/spring-logo.jpg","keywords":["Java 8","Reactive Streams","Spring","Spring Boot","SPRING DATA CASSANDRA","Spring WebFlux"],"articleSection":["Enterprise Java"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html","url":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html","name":"Doing stuff with Spring WebFlux - Java Code Geeks","isPartOf":{"@id":"https:\/\/www.javacodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#primaryimage"},"image":{"@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#primaryimage"},"thumbnailUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/spring-logo.jpg","datePublished":"2018-03-16T17:00:19+00:00","description":"Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring for quite a while. Since the release I have been seeing more and more","breadcrumb":{"@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#primaryimage","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/spring-logo.jpg","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2012\/10\/spring-logo.jpg","width":150,"height":150,"caption":"spring-interview-questions-answers"},{"@type":"BreadcrumbList","@id":"https:\/\/www.javacodegeeks.com\/2018\/03\/doing-stuff-with-spring-webflux.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.javacodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Java","item":"https:\/\/www.javacodegeeks.com\/category\/java"},{"@type":"ListItem","position":3,"name":"Enterprise Java","item":"https:\/\/www.javacodegeeks.com\/category\/java\/enterprise-java"},{"@type":"ListItem","position":4,"name":"Doing stuff with Spring WebFlux"}]},{"@type":"WebSite","@id":"https:\/\/www.javacodegeeks.com\/#website","url":"https:\/\/www.javacodegeeks.com\/","name":"Java Code Geeks","description":"Java Developers Resource Center","publisher":{"@id":"https:\/\/www.javacodegeeks.com\/#organization"},"alternateName":"JCG","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.javacodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.javacodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.javacodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.javacodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/javacodegeeks","https:\/\/x.com\/javacodegeeks"]},{"@type":"Person","@id":"https:\/\/www.javacodegeeks.com\/#\/schema\/person\/00ad664c44d777ebfa4d984dcf2717e2","name":"Dan Newton","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b5024a6405bf07e14dacc299779fc0abded33b42a05190784776429e2c76435f?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b5024a6405bf07e14dacc299779fc0abded33b42a05190784776429e2c76435f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b5024a6405bf07e14dacc299779fc0abded33b42a05190784776429e2c76435f?s=96&d=mm&r=g","caption":"Dan Newton"},"sameAs":["https:\/\/lankydanblog.com\/","https:\/\/www.linkedin.com\/in\/danknewton\/","https:\/\/x.com\/LankyDanDev"],"url":"https:\/\/www.javacodegeeks.com\/author\/dan-newton"}]}},"_links":{"self":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/74927","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/users\/12894"}],"replies":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/comments?post=74927"}],"version-history":[{"count":0,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/posts\/74927\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/media\/240"}],"wp:attachment":[{"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/media?parent=74927"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/categories?post=74927"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.javacodegeeks.com\/wp-json\/wp\/v2\/tags?post=74927"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}