diff --git a/pom.xml b/pom.xml
index b3da4655d6..88f54be629 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,6 +141,7 @@
persistence-modules/solr
spark-java
+ spring-5-reactive
spring-5-mvc
spring-activiti
spring-akka
diff --git a/spring-5-reactive/.gitignore b/spring-5-reactive/.gitignore
new file mode 100644
index 0000000000..dec013dfa4
--- /dev/null
+++ b/spring-5-reactive/.gitignore
@@ -0,0 +1,12 @@
+#folders#
+.idea
+/target
+/neoDb*
+/data
+/src/main/webapp/WEB-INF/classes
+*/META-INF/*
+
+# Packaged files #
+*.jar
+*.war
+*.ear
\ No newline at end of file
diff --git a/spring-5-reactive/README.md b/spring-5-reactive/README.md
new file mode 100644
index 0000000000..400e343263
--- /dev/null
+++ b/spring-5-reactive/README.md
@@ -0,0 +1,15 @@
+## Spring REST Example Project
+
+### The Course
+The "REST With Spring" Classes: http://bit.ly/restwithspring
+
+### Relevant Articles
+
+- [Concurrent Test Execution in Spring 5](http://www.baeldung.com/spring-5-concurrent-tests)
+- [Introduction to the Functional Web Framework in Spring 5](http://www.baeldung.com/spring-5-functional-web)
+- [Exploring the Spring 5 MVC URL Matching Improvements](http://www.baeldung.com/spring-5-mvc-url-matching)
+- [Spring 5 WebClient](http://www.baeldung.com/spring-5-webclient)
+- [Spring 5 Functional Bean Registration](http://www.baeldung.com/spring-5-functional-beans)
+- [The SpringJUnitConfig and SpringJUnitWebConfig Annotations in Spring 5](http://www.baeldung.com/spring-5-junit-config)
+- [Spring Security 5 for Reactive Applications](http://www.baeldung.com/spring-security-5-reactive)
+- [Spring 5 Testing with @EnabledIf Annotation](https://github.com/eugenp/tutorials/tree/master/spring-5)
diff --git a/spring-5-reactive/pom.xml b/spring-5-reactive/pom.xml
new file mode 100644
index 0000000000..43d9a5ed94
--- /dev/null
+++ b/spring-5-reactive/pom.xml
@@ -0,0 +1,203 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ spring-5-reactive
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-5
+ spring 5 sample project about new features
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.0.0.M7
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.projectreactor
+ reactor-spring
+ ${reactor-spring.version}
+
+
+ javax.json.bind
+ javax.json.bind-api
+ ${jsonb-api.version}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.geronimo.specs
+ geronimo-json_1.1_spec
+ ${geronimo-json_1.1_spec.version}
+
+
+ org.apache.johnzon
+ johnzon-jsonb
+ ${johnzon.version}
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.springframework
+ spring-test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.1
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.jupiter.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ ${junit.platform.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-runner
+ ${junit.platform.version}
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.Spring5Application
+ JAR
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 3
+ true
+ methods
+ true
+
+ **/*IntegrationTest.java
+ **/*LiveTest.java
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+ 1.0.0
+ 5.0.0
+ 2.20
+ 5.0.1.RELEASE
+ 1.0.1.RELEASE
+ 1.1.3
+ 1.0
+ 1.0
+
+
+
diff --git a/spring-5-reactive/src/main/java/com/baeldung/Spring5Application.java b/spring-5-reactive/src/main/java/com/baeldung/Spring5Application.java
new file mode 100644
index 0000000000..f321871646
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/Spring5Application.java
@@ -0,0 +1,15 @@
+package com.baeldung;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan(basePackages = { "com.baeldung.web" })
+public class Spring5Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Spring5Application.class, args);
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/SpringSecurity5Application.java b/spring-5-reactive/src/main/java/com/baeldung/SpringSecurity5Application.java
new file mode 100644
index 0000000000..02c91a1879
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/SpringSecurity5Application.java
@@ -0,0 +1,34 @@
+package com.baeldung;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
+import org.springframework.web.reactive.config.EnableWebFlux;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+import reactor.ipc.netty.NettyContext;
+import reactor.ipc.netty.http.server.HttpServer;
+
+@ComponentScan(basePackages = {"com.baeldung.security"})
+@EnableWebFlux
+public class SpringSecurity5Application {
+
+ public static void main(String[] args) {
+ try (AnnotationConfigApplicationContext context =
+ new AnnotationConfigApplicationContext(SpringSecurity5Application.class)) {
+ context.getBean(NettyContext.class).onClose().block();
+ }
+ }
+
+ @Bean
+ public NettyContext nettyContext(ApplicationContext context) {
+ HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context)
+ .build();
+ ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
+ HttpServer httpServer = HttpServer.create("localhost", 8080);
+ return httpServer.newHandler(adapter).block();
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/Actor.java b/spring-5-reactive/src/main/java/com/baeldung/functional/Actor.java
new file mode 100644
index 0000000000..23c88b89e1
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/Actor.java
@@ -0,0 +1,23 @@
+package com.baeldung.functional;
+
+class Actor {
+ private String firstname;
+ private String lastname;
+
+ public Actor() {
+ }
+
+ public Actor(String firstname, String lastname) {
+ this.firstname = firstname;
+ this.lastname = lastname;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/ExploreSpring5URLPatternUsingRouterFunctions.java b/spring-5-reactive/src/main/java/com/baeldung/functional/ExploreSpring5URLPatternUsingRouterFunctions.java
new file mode 100644
index 0000000000..2a6d04538c
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/ExploreSpring5URLPatternUsingRouterFunctions.java
@@ -0,0 +1,61 @@
+package com.baeldung.functional;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+
+public class ExploreSpring5URLPatternUsingRouterFunctions {
+
+ private RouterFunction routingFunction() {
+
+ return route(GET("/p?ths"), serverRequest -> ok().body(fromObject("/p?ths"))).andRoute(GET("/test/{*id}"), serverRequest -> ok().body(fromObject(serverRequest.pathVariable("id"))))
+ .andRoute(GET("/*card"), serverRequest -> ok().body(fromObject("/*card path was accessed")))
+ .andRoute(GET("/{var1}_{var2}"), serverRequest -> ok().body(fromObject(serverRequest.pathVariable("var1") + " , " + serverRequest.pathVariable("var2"))))
+ .andRoute(GET("/{baeldung:[a-z]+}"), serverRequest -> ok().body(fromObject("/{baeldung:[a-z]+} was accessed and baeldung=" + serverRequest.pathVariable("baeldung"))))
+ .and(RouterFunctions.resources("/files/{*filepaths}", new ClassPathResource("files/")));
+ }
+
+ WebServer start() throws Exception {
+ WebHandler webHandler = (WebHandler) toHttpHandler(routingFunction());
+ HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(webHandler)
+ .filter(new IndexRewriteFilter())
+ .build();
+
+ Tomcat tomcat = new Tomcat();
+ tomcat.setHostname("localhost");
+ tomcat.setPort(9090);
+ Context rootContext = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+ ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
+ Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
+ rootContext.addServletMappingDecoded("/", "httpHandlerServlet");
+
+ TomcatWebServer server = new TomcatWebServer(tomcat);
+ server.start();
+ return server;
+
+ }
+
+ public static void main(String[] args) {
+ try {
+ new FunctionalWebApplication().start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/FormHandler.java b/spring-5-reactive/src/main/java/com/baeldung/functional/FormHandler.java
new file mode 100644
index 0000000000..05069735bb
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/FormHandler.java
@@ -0,0 +1,41 @@
+package com.baeldung.functional;
+
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
+import static org.springframework.web.reactive.function.BodyExtractors.toFormData;
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+public class FormHandler {
+
+ Mono handleLogin(ServerRequest request) {
+ return request.body(toFormData())
+ .map(MultiValueMap::toSingleValueMap)
+ .filter(formData -> "baeldung".equals(formData.get("user")))
+ .filter(formData -> "you_know_what_to_do".equals(formData.get("token")))
+ .flatMap(formData -> ok().body(Mono.just("welcome back!"), String.class))
+ .switchIfEmpty(ServerResponse.badRequest()
+ .build());
+ }
+
+ Mono handleUpload(ServerRequest request) {
+ return request.body(toDataBuffers())
+ .collectList()
+ .flatMap(dataBuffers -> ok().body(fromObject(extractData(dataBuffers).toString())));
+ }
+
+ private AtomicLong extractData(List dataBuffers) {
+ AtomicLong atomicLong = new AtomicLong(0);
+ dataBuffers.forEach(d -> atomicLong.addAndGet(d.asByteBuffer()
+ .array().length));
+ return atomicLong;
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java b/spring-5-reactive/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java
new file mode 100644
index 0000000000..402b607b19
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java
@@ -0,0 +1,87 @@
+package com.baeldung.functional;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
+import static org.springframework.web.reactive.function.server.RequestPredicates.path;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+
+import reactor.core.publisher.Flux;
+
+@SpringBootApplication
+@ComponentScan(basePackages = { "com.baeldung.functional" })
+public class FunctionalSpringBootApplication {
+
+ private static final Actor BRAD_PITT = new Actor("Brad", "Pitt");
+ private static final Actor TOM_HANKS = new Actor("Tom", "Hanks");
+ private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS));
+
+ private RouterFunction routingFunction() {
+ FormHandler formHandler = new FormHandler();
+
+ RouterFunction restfulRouter = route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest.bodyToMono(Actor.class)
+ .doOnNext(actors::add)
+ .then(ok().build()));
+
+ return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld"))).andRoute(POST("/login"), formHandler::handleLogin)
+ .andRoute(POST("/upload"), formHandler::handleUpload)
+ .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/")))
+ .andNest(path("/actor"), restfulRouter)
+ .filter((request, next) -> {
+ System.out.println("Before handler invocation: " + request.path());
+ return next.handle(request);
+ });
+ }
+
+ @Bean
+ public ServletRegistrationBean servletRegistrationBean() throws Exception {
+ HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler((WebHandler) toHttpHandler(routingFunction()))
+ .filter(new IndexRewriteFilter())
+ .build();
+ ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new RootServlet(httpHandler), "/");
+ registrationBean.setLoadOnStartup(1);
+ registrationBean.setAsyncSupported(true);
+ return registrationBean;
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ @Profile("!https")
+ static class SecurityConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(final HttpSecurity http) throws Exception {
+ http.authorizeRequests()
+ .anyRequest()
+ .permitAll();
+ }
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(FunctionalSpringBootApplication.class, args);
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/FunctionalWebApplication.java b/spring-5-reactive/src/main/java/com/baeldung/functional/FunctionalWebApplication.java
new file mode 100644
index 0000000000..5a7d70d3db
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/FunctionalWebApplication.java
@@ -0,0 +1,80 @@
+package com.baeldung.functional;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
+import static org.springframework.web.reactive.function.server.RequestPredicates.path;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+
+import reactor.core.publisher.Flux;
+
+public class FunctionalWebApplication {
+
+ private static final Actor BRAD_PITT = new Actor("Brad", "Pitt");
+ private static final Actor TOM_HANKS = new Actor("Tom", "Hanks");
+ private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS));
+
+ private RouterFunction routingFunction() {
+ FormHandler formHandler = new FormHandler();
+
+ RouterFunction restfulRouter = route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest.bodyToMono(Actor.class)
+ .doOnNext(actors::add)
+ .then(ok().build()));
+
+ return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld"))).andRoute(POST("/login"), formHandler::handleLogin)
+ .andRoute(POST("/upload"), formHandler::handleUpload)
+ .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/")))
+ .andNest(path("/actor"), restfulRouter)
+ .filter((request, next) -> {
+ System.out.println("Before handler invocation: " + request.path());
+ return next.handle(request);
+ });
+ }
+
+ WebServer start() throws Exception {
+ WebHandler webHandler = (WebHandler) toHttpHandler(routingFunction());
+ HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(webHandler)
+ .filter(new IndexRewriteFilter())
+ .build();
+
+ Tomcat tomcat = new Tomcat();
+ tomcat.setHostname("localhost");
+ tomcat.setPort(9090);
+ Context rootContext = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+ ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
+ Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
+ rootContext.addServletMappingDecoded("/", "httpHandlerServlet");
+
+ TomcatWebServer server = new TomcatWebServer(tomcat);
+ server.start();
+ return server;
+
+ }
+
+ public static void main(String[] args) {
+ try {
+ new FunctionalWebApplication().start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/IndexRewriteFilter.java b/spring-5-reactive/src/main/java/com/baeldung/functional/IndexRewriteFilter.java
new file mode 100644
index 0000000000..3e91a0354b
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/IndexRewriteFilter.java
@@ -0,0 +1,27 @@
+package com.baeldung.functional;
+
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+class IndexRewriteFilter implements WebFilter {
+
+ @Override
+ public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
+ ServerHttpRequest request = serverWebExchange.getRequest();
+ if (request.getURI()
+ .getPath()
+ .equals("/")) {
+ return webFilterChain.filter(serverWebExchange.mutate()
+ .request(builder -> builder.method(request.getMethod())
+ .contextPath(request.getPath()
+ .toString())
+ .path("/test"))
+ .build());
+ }
+ return webFilterChain.filter(serverWebExchange);
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/MyService.java b/spring-5-reactive/src/main/java/com/baeldung/functional/MyService.java
new file mode 100644
index 0000000000..b7b8b13d8b
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/MyService.java
@@ -0,0 +1,11 @@
+package com.baeldung.functional;
+
+import java.util.Random;
+
+public class MyService {
+
+ public int getRandomNumber() {
+ return (new Random().nextInt(10));
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/functional/RootServlet.java b/spring-5-reactive/src/main/java/com/baeldung/functional/RootServlet.java
new file mode 100644
index 0000000000..8fe24821de
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/functional/RootServlet.java
@@ -0,0 +1,82 @@
+package com.baeldung.functional;
+
+import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
+import static org.springframework.web.reactive.function.BodyExtractors.toFormData;
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
+import static org.springframework.web.reactive.function.server.RequestPredicates.path;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class RootServlet extends ServletHttpHandlerAdapter {
+
+ public RootServlet() {
+ this(WebHttpHandlerBuilder.webHandler((WebHandler) toHttpHandler(routingFunction()))
+ .filter(new IndexRewriteFilter())
+ .build());
+ }
+
+ RootServlet(HttpHandler httpHandler) {
+ super(httpHandler);
+ }
+
+ private static final Actor BRAD_PITT = new Actor("Brad", "Pitt");
+ private static final Actor TOM_HANKS = new Actor("Tom", "Hanks");
+ private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS));
+
+ private static RouterFunction> routingFunction() {
+
+ return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld"))).andRoute(POST("/login"), serverRequest -> serverRequest.body(toFormData())
+ .map(MultiValueMap::toSingleValueMap)
+ .map(formData -> {
+ System.out.println("form data: " + formData.toString());
+ if ("baeldung".equals(formData.get("user")) && "you_know_what_to_do".equals(formData.get("token"))) {
+ return ok().body(Mono.just("welcome back!"), String.class)
+ .block();
+ }
+ return ServerResponse.badRequest()
+ .build()
+ .block();
+ }))
+ .andRoute(POST("/upload"), serverRequest -> serverRequest.body(toDataBuffers())
+ .collectList()
+ .map(dataBuffers -> {
+ AtomicLong atomicLong = new AtomicLong(0);
+ dataBuffers.forEach(d -> atomicLong.addAndGet(d.asByteBuffer()
+ .array().length));
+ System.out.println("data length:" + atomicLong.get());
+ return ok().body(fromObject(atomicLong.toString()))
+ .block();
+ }))
+ .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/")))
+ .andNest(path("/actor"), route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest.bodyToMono(Actor.class)
+ .doOnNext(actors::add)
+ .then(ok().build())))
+ .filter((request, next) -> {
+ System.out.println("Before handler invocation: " + request.path());
+ return next.handle(request);
+ });
+
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jsonb/Person.java b/spring-5-reactive/src/main/java/com/baeldung/jsonb/Person.java
new file mode 100644
index 0000000000..7a54b37574
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jsonb/Person.java
@@ -0,0 +1,127 @@
+package com.baeldung.jsonb;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+import javax.json.bind.annotation.JsonbDateFormat;
+import javax.json.bind.annotation.JsonbNumberFormat;
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbTransient;
+
+public class Person {
+
+ private int id;
+ @JsonbProperty("person-name")
+ private String name;
+ @JsonbProperty(nillable = true)
+ private String email;
+ @JsonbTransient
+ private int age;
+ @JsonbDateFormat("dd-MM-yyyy")
+ private LocalDate registeredDate;
+ private BigDecimal salary;
+
+ public Person() {
+ }
+
+ public Person(int id, String name, String email, int age, LocalDate registeredDate, BigDecimal salary) {
+ super();
+ this.id = id;
+ this.name = name;
+ this.email = email;
+ this.age = age;
+ this.registeredDate = registeredDate;
+ this.salary = salary;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @JsonbNumberFormat(locale = "en_US", value = "#0.0")
+ public BigDecimal getSalary() {
+ return salary;
+ }
+
+ public void setSalary(BigDecimal salary) {
+ this.salary = salary;
+ }
+
+ public LocalDate getRegisteredDate() {
+ return registeredDate;
+ }
+
+ public void setRegisteredDate(LocalDate registeredDate) {
+ this.registeredDate = registeredDate;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Person [id=");
+ builder.append(id);
+ builder.append(", name=");
+ builder.append(name);
+ builder.append(", email=");
+ builder.append(email);
+ builder.append(", age=");
+ builder.append(age);
+ builder.append(", registeredDate=");
+ builder.append(registeredDate);
+ builder.append(", salary=");
+ builder.append(salary);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Person other = (Person) obj;
+ if (id != other.id)
+ return false;
+ return true;
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jsonb/PersonController.java b/spring-5-reactive/src/main/java/com/baeldung/jsonb/PersonController.java
new file mode 100644
index 0000000000..e216a282eb
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jsonb/PersonController.java
@@ -0,0 +1,58 @@
+package com.baeldung.jsonb;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController("/person")
+public class PersonController {
+
+ List personRepository;
+
+ @PostConstruct
+ public void init() {
+ // @formatter:off
+ personRepository = new ArrayList<>(Arrays.asList(
+ new Person(1, "Jhon", "jhon@test.com", 20, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1000)),
+ new Person(2, "Jhon", "jhon1@test.com", 20, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1500)),
+ new Person(3, "Jhon", null, 20, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1000)),
+ new Person(4, "Tom", "tom@test.com", 21, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1500)),
+ new Person(5, "Mark", "mark@test.com", 21, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1200)),
+ new Person(6, "Julia", "jhon@test.com", 20, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1000))));
+ // @formatter:on
+
+ }
+
+ @GetMapping("/person/{id}")
+ @ResponseBody
+ public Person findById(@PathVariable final int id) {
+ return personRepository.get(id);
+ }
+
+ @PostMapping("/person")
+ @ResponseStatus(HttpStatus.OK)
+ @ResponseBody
+ public boolean insertPerson(@RequestBody final Person person) {
+ return personRepository.add(person);
+ }
+
+ @GetMapping("/person")
+ @ResponseBody
+ public List findAll() {
+ return personRepository;
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jsonb/Spring5Application.java b/spring-5-reactive/src/main/java/com/baeldung/jsonb/Spring5Application.java
new file mode 100644
index 0000000000..00fce06834
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jsonb/Spring5Application.java
@@ -0,0 +1,30 @@
+package com.baeldung.jsonb;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.JsonbHttpMessageConverter;
+
+@SpringBootApplication
+@ComponentScan(basePackages = { "com.baeldung.jsonb" })
+public class Spring5Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Spring5Application.class, args);
+ }
+
+ @Bean
+ public HttpMessageConverters customConverters() {
+ Collection> messageConverters = new ArrayList<>();
+ JsonbHttpMessageConverter jsonbHttpMessageConverter = new JsonbHttpMessageConverter();
+ messageConverters.add(jsonbHttpMessageConverter);
+ return new HttpMessageConverters(true, messageConverters);
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jupiter/MethodParameterFactory.java b/spring-5-reactive/src/main/java/com/baeldung/jupiter/MethodParameterFactory.java
new file mode 100644
index 0000000000..85bd505d11
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jupiter/MethodParameterFactory.java
@@ -0,0 +1,46 @@
+package com.baeldung.jupiter;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+abstract class MethodParameterFactory {
+
+ private MethodParameterFactory() {
+ }
+
+ public static MethodParameter createMethodParameter(Parameter parameter) {
+ Assert.notNull(parameter, "Parameter must not be null");
+ Executable executable = parameter.getDeclaringExecutable();
+ if (executable instanceof Method) {
+ return new MethodParameter((Method) executable, getIndex(parameter));
+ }
+ return new MethodParameter((Constructor>) executable, getIndex(parameter));
+ }
+
+ public static SynthesizingMethodParameter createSynthesizingMethodParameter(Parameter parameter) {
+ Assert.notNull(parameter, "Parameter must not be null");
+ Executable executable = parameter.getDeclaringExecutable();
+ if (executable instanceof Method) {
+ return new SynthesizingMethodParameter((Method) executable, getIndex(parameter));
+ }
+ throw new UnsupportedOperationException("Cannot create a SynthesizingMethodParameter for a constructor parameter: " + parameter);
+ }
+
+ private static int getIndex(Parameter parameter) {
+ Assert.notNull(parameter, "Parameter must not be null");
+ Executable executable = parameter.getDeclaringExecutable();
+ Parameter[] parameters = executable.getParameters();
+ for (int i = 0; i < parameters.length; i++) {
+ if (parameters[i] == parameter) {
+ return i;
+ }
+ }
+ throw new IllegalStateException(String.format("Failed to resolve index of parameter [%s] in executable [%s]", parameter, executable.toGenericString()));
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jupiter/ParameterAutowireUtils.java b/spring-5-reactive/src/main/java/com/baeldung/jupiter/ParameterAutowireUtils.java
new file mode 100644
index 0000000000..d1a4cc8b29
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jupiter/ParameterAutowireUtils.java
@@ -0,0 +1,44 @@
+package com.baeldung.jupiter;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.DependencyDescriptor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Optional;
+
+import static org.springframework.core.annotation.AnnotatedElementUtils.hasAnnotation;
+
+abstract class ParameterAutowireUtils {
+
+ private ParameterAutowireUtils() {
+ }
+
+ public static boolean isAutowirable(Parameter parameter) {
+ return ApplicationContext.class.isAssignableFrom(parameter.getType()) || hasAnnotation(parameter, Autowired.class) || hasAnnotation(parameter, Qualifier.class) || hasAnnotation(parameter, Value.class);
+ }
+
+ public static Object resolveDependency(Parameter parameter, Class> containingClass, ApplicationContext applicationContext) {
+
+ boolean required = findMergedAnnotation(parameter, Autowired.class).map(Autowired::required)
+ .orElse(true);
+ MethodParameter methodParameter = (parameter.getDeclaringExecutable() instanceof Method ? MethodParameterFactory.createSynthesizingMethodParameter(parameter) : MethodParameterFactory.createMethodParameter(parameter));
+ DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required);
+ descriptor.setContainingClass(containingClass);
+
+ return applicationContext.getAutowireCapableBeanFactory()
+ .resolveDependency(descriptor, null);
+ }
+
+ private static Optional findMergedAnnotation(AnnotatedElement element, Class annotationType) {
+
+ return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(element, annotationType));
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jupiter/SpringExtension.java b/spring-5-reactive/src/main/java/com/baeldung/jupiter/SpringExtension.java
new file mode 100644
index 0000000000..7218d984ef
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jupiter/SpringExtension.java
@@ -0,0 +1,94 @@
+package com.baeldung.jupiter;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.TestInstancePostProcessor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.test.context.TestContextManager;
+import org.springframework.util.Assert;
+
+public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback, ParameterResolver {
+
+ private static final ExtensionContext.Namespace namespace = ExtensionContext.Namespace.create(SpringExtension.class);
+
+ @Override
+ public void beforeAll(ExtensionContext context) throws Exception {
+ getTestContextManager(context).beforeTestClass();
+ }
+
+ @Override
+ public void afterAll(ExtensionContext context) throws Exception {
+ try {
+ getTestContextManager(context).afterTestClass();
+ } finally {
+ context.getStore(namespace)
+ .remove(context.getTestClass()
+ .get());
+ }
+ }
+
+ @Override
+ public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
+ getTestContextManager(context).prepareTestInstance(testInstance);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ Object testInstance = context.getTestInstance();
+ Method testMethod = context.getTestMethod()
+ .get();
+ getTestContextManager(context).beforeTestMethod(testInstance, testMethod);
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) throws Exception {
+ Object testInstance = context.getTestInstance();
+ Method testMethod = context.getTestMethod()
+ .get();
+ Throwable testException = context.getExecutionException()
+ .orElse(null);
+ getTestContextManager(context).afterTestMethod(testInstance, testMethod, testException);
+ }
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ Parameter parameter = parameterContext.getParameter();
+ Executable executable = parameter.getDeclaringExecutable();
+ return ((executable instanceof Constructor) && AnnotatedElementUtils.hasAnnotation(executable, Autowired.class)) || ParameterAutowireUtils.isAutowirable(parameter);
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ Parameter parameter = parameterContext.getParameter();
+ Class> testClass = extensionContext.getTestClass()
+ .get();
+ ApplicationContext applicationContext = getApplicationContext(extensionContext);
+ return ParameterAutowireUtils.resolveDependency(parameter, testClass, applicationContext);
+ }
+
+ private ApplicationContext getApplicationContext(ExtensionContext context) {
+ return getTestContextManager(context).getTestContext()
+ .getApplicationContext();
+ }
+
+ private TestContextManager getTestContextManager(ExtensionContext context) {
+ Assert.notNull(context, "ExtensionContext must not be null");
+ Class> testClass = context.getTestClass()
+ .get();
+ ExtensionContext.Store store = context.getStore(namespace);
+ return store.getOrComputeIfAbsent(testClass, TestContextManager::new, TestContextManager.class);
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jupiter/SpringJUnit5Config.java b/spring-5-reactive/src/main/java/com/baeldung/jupiter/SpringJUnit5Config.java
new file mode 100644
index 0000000000..8f02d71d49
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jupiter/SpringJUnit5Config.java
@@ -0,0 +1,39 @@
+package com.baeldung.jupiter;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.lang.annotation.*;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SpringJUnit5Config {
+
+ @AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
+ Class>[] value() default {};
+
+ @AliasFor(annotation = ContextConfiguration.class)
+ Class>[] classes() default {};
+
+ @AliasFor(annotation = ContextConfiguration.class)
+ String[] locations() default {};
+
+ @AliasFor(annotation = ContextConfiguration.class)
+ Class extends ApplicationContextInitializer extends ConfigurableApplicationContext>>[] initializers() default {};
+
+ @AliasFor(annotation = ContextConfiguration.class)
+ boolean inheritLocations() default true;
+
+ @AliasFor(annotation = ContextConfiguration.class)
+ boolean inheritInitializers() default true;
+
+ @AliasFor(annotation = ContextConfiguration.class)
+ String name() default "";
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/jupiter/TestConfig.java b/spring-5-reactive/src/main/java/com/baeldung/jupiter/TestConfig.java
new file mode 100644
index 0000000000..a29f77c5df
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/jupiter/TestConfig.java
@@ -0,0 +1,20 @@
+package com.baeldung.jupiter;
+
+import com.baeldung.web.reactive.Task;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+
+@Configuration
+public class TestConfig {
+
+ @Bean
+ static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
+
+ @Bean
+ Task taskName() {
+ return new Task("taskName", 1);
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/persistence/DataSetupBean.java b/spring-5-reactive/src/main/java/com/baeldung/persistence/DataSetupBean.java
new file mode 100644
index 0000000000..9f5d9ff6c2
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/persistence/DataSetupBean.java
@@ -0,0 +1,27 @@
+package com.baeldung.persistence;
+
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
+
+import java.util.stream.IntStream;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.baeldung.web.Foo;
+
+@Component
+public class DataSetupBean implements InitializingBean {
+
+ @Autowired
+ private FooRepository repo;
+
+ //
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ IntStream.range(1, 20)
+ .forEach(i -> repo.save(new Foo(randomAlphabetic(8))));
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/persistence/FooRepository.java b/spring-5-reactive/src/main/java/com/baeldung/persistence/FooRepository.java
new file mode 100644
index 0000000000..1f1e071158
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/persistence/FooRepository.java
@@ -0,0 +1,10 @@
+package com.baeldung.persistence;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import com.baeldung.web.Foo;
+
+public interface FooRepository extends JpaRepository, JpaSpecificationExecutor {
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/security/GreetController.java b/spring-5-reactive/src/main/java/com/baeldung/security/GreetController.java
new file mode 100644
index 0000000000..6b69e3bc9b
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/security/GreetController.java
@@ -0,0 +1,37 @@
+package com.baeldung.security;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.security.Principal;
+
+@RestController
+public class GreetController {
+
+ private GreetService greetService;
+
+ public GreetController(GreetService greetService) {
+ this.greetService = greetService;
+ }
+
+ @GetMapping("/")
+ public Mono greet(Mono principal) {
+ return principal
+ .map(Principal::getName)
+ .map(name -> String.format("Hello, %s", name));
+ }
+
+ @GetMapping("/admin")
+ public Mono greetAdmin(Mono principal) {
+ return principal
+ .map(Principal::getName)
+ .map(name -> String.format("Admin access: %s", name));
+ }
+
+ @GetMapping("/greetService")
+ public Mono greetService() {
+ return greetService.greet();
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/security/GreetService.java b/spring-5-reactive/src/main/java/com/baeldung/security/GreetService.java
new file mode 100644
index 0000000000..7622b360be
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/security/GreetService.java
@@ -0,0 +1,15 @@
+package com.baeldung.security;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+public class GreetService {
+
+ @PreAuthorize("hasRole('ADMIN')")
+ public Mono greet() {
+ return Mono.just("Hello from service!");
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/security/SecurityConfig.java b/spring-5-reactive/src/main/java/com/baeldung/security/SecurityConfig.java
new file mode 100644
index 0000000000..a9e44a2eee
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/security/SecurityConfig.java
@@ -0,0 +1,42 @@
+package com.baeldung.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class SecurityConfig {
+
+ @Bean
+ public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
+ return http.authorizeExchange()
+ .pathMatchers("/admin").hasAuthority("ROLE_ADMIN")
+ .anyExchange().authenticated()
+ .and().formLogin()
+ .and().build();
+ }
+
+ @Bean
+ public MapReactiveUserDetailsService userDetailsService() {
+ UserDetails user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build();
+
+ UserDetails admin = User.withDefaultPasswordEncoder()
+ .username("admin")
+ .password("password")
+ .roles("ADMIN")
+ .build();
+
+ return new MapReactiveUserDetailsService(user, admin);
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/Foo.java b/spring-5-reactive/src/main/java/com/baeldung/web/Foo.java
new file mode 100644
index 0000000000..c4868a9958
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/web/Foo.java
@@ -0,0 +1,84 @@
+package com.baeldung.web;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Foo {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long id;
+
+ private String name;
+
+ public Foo() {
+ super();
+ }
+
+ public Foo(final String name) {
+ super();
+
+ this.name = name;
+ }
+
+ public Foo(final long id, final String name) {
+ super();
+
+ this.id = id;
+ this.name = name;
+ }
+
+ // API
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(final long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ //
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Foo other = (Foo) obj;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Foo [name=" + name + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/FooController.java b/spring-5-reactive/src/main/java/com/baeldung/web/FooController.java
new file mode 100644
index 0000000000..925f2b49f4
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/web/FooController.java
@@ -0,0 +1,53 @@
+package com.baeldung.web;
+
+import com.baeldung.persistence.FooRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import java.util.List;
+
+@RestController("/foos")
+public class FooController {
+
+ @Autowired
+ private FooRepository repo;
+
+ // API - read
+
+ @GetMapping("/foos/{id}")
+ @ResponseBody
+ @Validated
+ public Foo findById(@PathVariable @Min(0) final long id) {
+ return repo.findById(id)
+ .orElse(null);
+ }
+
+ @GetMapping
+ @ResponseBody
+ public List findAll() {
+ return repo.findAll();
+ }
+
+ @GetMapping(params = { "page", "size" })
+ @ResponseBody
+ @Validated
+ public List findPaginated(@RequestParam("page") @Min(0) final int page, @Max(100) @RequestParam("size") final int size) {
+ return repo.findAll(PageRequest.of(page, size))
+ .getContent();
+ }
+
+ // API - write
+
+ @PutMapping("/foos/{id}")
+ @ResponseStatus(HttpStatus.OK)
+ @ResponseBody
+ public Foo update(@PathVariable("id") final String id, @RequestBody final Foo foo) {
+ return foo;
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/PathPatternController.java b/spring-5-reactive/src/main/java/com/baeldung/web/PathPatternController.java
new file mode 100644
index 0000000000..fa413e4f00
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/web/PathPatternController.java
@@ -0,0 +1,39 @@
+package com.baeldung.web;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class PathPatternController {
+
+ @GetMapping("/spring5/{*id}")
+ public String URIVariableHandler(@PathVariable String id) {
+ return id;
+ }
+
+ @GetMapping("/s?ring5")
+ public String wildcardTakingExactlyOneChar() {
+ return "/s?ring5";
+ }
+
+ @GetMapping("/spring5/*id")
+ public String wildcardTakingZeroOrMoreChar() {
+ return "/spring5/*id";
+ }
+
+ @GetMapping("/resources/**")
+ public String wildcardTakingZeroOrMorePathSegments() {
+ return "/resources/**";
+ }
+
+ @GetMapping("/{baeldung:[a-z]+}")
+ public String regexInPathVariable(@PathVariable String baeldung) {
+ return baeldung;
+ }
+
+ @GetMapping("/{var1}_{var2}")
+ public String multiplePathVariablesInSameSegment(@PathVariable String var1, @PathVariable String var2) {
+ return "Two variables are var1=" + var1 + " and var2=" + var2;
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/Task.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/Task.java
new file mode 100644
index 0000000000..725fd931e1
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/Task.java
@@ -0,0 +1,28 @@
+package com.baeldung.web.reactive;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Task {
+
+ private final String name;
+
+ private final int id;
+
+ public Task(@JsonProperty("name") String name, @JsonProperty("id") int id) {
+ this.name = name;
+ this.id = id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public int getId() {
+ return this.id;
+ }
+
+ @Override
+ public String toString() {
+ return "Task{" + "name='" + name + '\'' + ", id=" + id + '}';
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java
new file mode 100644
index 0000000000..a218c6b7cf
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java
@@ -0,0 +1,85 @@
+package com.baeldung.web.reactive.client;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.springframework.http.*;
+import org.springframework.http.client.reactive.ClientHttpRequest;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.reactive.function.BodyInserter;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.time.ZonedDateTime;
+import java.util.Collections;
+
+@RestController
+public class WebClientController {
+
+ @ResponseStatus(HttpStatus.OK)
+ @GetMapping("/resource")
+ public void getResource() {
+ }
+
+ public void demonstrateWebClient() {
+ // request
+ WebClient.UriSpec request1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST);
+ WebClient.UriSpec request2 = createWebClientWithServerURLAndDefaultValues().post();
+
+ // request body specifications
+ WebClient.RequestBodySpec uri1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST)
+ .uri("/resource");
+ WebClient.RequestBodySpec uri2 = createWebClientWithServerURLAndDefaultValues().post()
+ .uri(URI.create("/resource"));
+
+ // request header specification
+ WebClient.RequestHeadersSpec> requestSpec1 = uri1.body(BodyInserters.fromPublisher(Mono.just("data"), String.class));
+ WebClient.RequestHeadersSpec> requestSpec2 = uri2.body(BodyInserters.fromObject("data"));
+
+ // inserters
+ BodyInserter, ReactiveHttpOutputMessage> inserter1 = BodyInserters
+ .fromPublisher(Subscriber::onComplete, String.class);
+
+ LinkedMultiValueMap map = new LinkedMultiValueMap<>();
+ map.add("key1", "value1");
+ map.add("key2", "value2");
+
+ BodyInserter, ClientHttpRequest> inserter2 = BodyInserters.fromMultipartData(map);
+ BodyInserter inserter3 = BodyInserters.fromObject("body");
+
+ // responses
+ WebClient.ResponseSpec response1 = uri1.body(inserter3)
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+ .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
+ .acceptCharset(Charset.forName("UTF-8"))
+ .ifNoneMatch("*")
+ .ifModifiedSince(ZonedDateTime.now())
+ .retrieve();
+ WebClient.ResponseSpec response2 = requestSpec2.retrieve();
+
+ }
+
+ private WebClient createWebClient() {
+ return WebClient.create();
+ }
+
+ private WebClient createWebClientWithServerURL() {
+ return WebClient.create("http://localhost:8081");
+ }
+
+ private WebClient createWebClientWithServerURLAndDefaultValues() {
+ return WebClient.builder()
+ .baseUrl("http://localhost:8081")
+ .defaultCookie("cookieKey", "cookieValue")
+ .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+ .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
+ .build();
+ }
+
+}
diff --git a/spring-5-reactive/src/main/resources/application.properties b/spring-5-reactive/src/main/resources/application.properties
new file mode 100644
index 0000000000..ccec014c2b
--- /dev/null
+++ b/spring-5-reactive/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+server.port=8081
+
+logging.level.root=INFO
\ No newline at end of file
diff --git a/spring-5-reactive/src/main/resources/files/hello.txt b/spring-5-reactive/src/main/resources/files/hello.txt
new file mode 100644
index 0000000000..b6fc4c620b
--- /dev/null
+++ b/spring-5-reactive/src/main/resources/files/hello.txt
@@ -0,0 +1 @@
+hello
\ No newline at end of file
diff --git a/spring-5-reactive/src/main/resources/files/test/test.txt b/spring-5-reactive/src/main/resources/files/test/test.txt
new file mode 100644
index 0000000000..30d74d2584
--- /dev/null
+++ b/spring-5-reactive/src/main/resources/files/test/test.txt
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/spring-5-reactive/src/main/webapp/WEB-INF/web.xml b/spring-5-reactive/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..bfcf43dad2
--- /dev/null
+++ b/spring-5-reactive/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,21 @@
+
+
+
+ Spring Functional Application
+
+
+ functional
+ com.baeldung.functional.RootServlet
+ 1
+ true
+
+
+ functional
+ /
+
+
+
+
\ No newline at end of file
diff --git a/spring-5-reactive/src/test/java/com/baeldung/Example1IntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/Example1IntegrationTest.java
new file mode 100644
index 0000000000..8b9e66213f
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/Example1IntegrationTest.java
@@ -0,0 +1,29 @@
+package com.baeldung;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class Example1IntegrationTest {
+
+ @Test
+ public void test1a() {
+ block(3000);
+ }
+
+ @Test
+ public void test1b() {
+ block(3000);
+ }
+
+ public static void block(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ System.out.println("Thread interrupted");
+ }
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/Example2IntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/Example2IntegrationTest.java
new file mode 100644
index 0000000000..6ed53ca4e9
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/Example2IntegrationTest.java
@@ -0,0 +1,29 @@
+package com.baeldung;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class Example2IntegrationTest {
+
+ @Test
+ public void test1a() {
+ block(3000);
+ }
+
+ @Test
+ public void test1b() {
+ block(3000);
+ }
+
+ public static void block(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ System.out.println("Thread Interrupted");
+ }
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/ParallelIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/ParallelIntegrationTest.java
new file mode 100644
index 0000000000..1ce96de4ef
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/ParallelIntegrationTest.java
@@ -0,0 +1,24 @@
+package com.baeldung;
+
+import org.junit.Test;
+import org.junit.experimental.ParallelComputer;
+import org.junit.runner.Computer;
+import org.junit.runner.JUnitCore;
+
+public class ParallelIntegrationTest {
+
+ @Test
+ public void runTests() {
+ final Class>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
+
+ JUnitCore.runClasses(new Computer(), classes);
+ }
+
+ @Test
+ public void runTestsInParallel() {
+ final Class>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
+
+ JUnitCore.runClasses(new ParallelComputer(true, true), classes);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-5-reactive/src/test/java/com/baeldung/Spring5ApplicationIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/Spring5ApplicationIntegrationTest.java
new file mode 100644
index 0000000000..af288c3c2d
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/Spring5ApplicationIntegrationTest.java
@@ -0,0 +1,16 @@
+package com.baeldung;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class Spring5ApplicationIntegrationTest {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/Spring5JUnit4ConcurrentIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/Spring5JUnit4ConcurrentIntegrationTest.java
new file mode 100644
index 0000000000..7e494465b2
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/Spring5JUnit4ConcurrentIntegrationTest.java
@@ -0,0 +1,52 @@
+package com.baeldung;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = Spring5JUnit4ConcurrentIntegrationTest.SimpleConfiguration.class)
+public class Spring5JUnit4ConcurrentIntegrationTest implements ApplicationContextAware, InitializingBean {
+
+ @Configuration
+ public static class SimpleConfiguration {
+ }
+
+ private ApplicationContext applicationContext;
+
+ private boolean beanInitialized = false;
+
+ @Override
+ public final void afterPropertiesSet() throws Exception {
+ this.beanInitialized = true;
+ }
+
+ @Override
+ public final void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @Test
+ public final void verifyApplicationContextSet() throws InterruptedException {
+ TimeUnit.SECONDS.sleep(2);
+ assertNotNull("The application context should have been set due to ApplicationContextAware semantics.", this.applicationContext);
+ }
+
+ @Test
+ public final void verifyBeanInitialized() throws InterruptedException {
+ TimeUnit.SECONDS.sleep(2);
+ assertTrue("This test bean should have been initialized due to InitializingBean semantics.", this.beanInitialized);
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/functional/BeanRegistrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/functional/BeanRegistrationTest.java
new file mode 100644
index 0000000000..0b1542dbbc
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/functional/BeanRegistrationTest.java
@@ -0,0 +1,42 @@
+package com.baeldung.functional;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+import com.baeldung.Spring5Application;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Spring5Application.class)
+public class BeanRegistrationTest {
+
+ @Autowired
+ private GenericWebApplicationContext context;
+
+ @Test
+ public void whenRegisterBean_thenOk() {
+ context.registerBean(MyService.class, () -> new MyService());
+ MyService myService = (MyService) context.getBean("com.baeldung.functional.MyService");
+ assertTrue(myService.getRandomNumber() < 10);
+ }
+
+ @Test
+ public void whenRegisterBeanWithName_thenOk() {
+ context.registerBean("mySecondService", MyService.class, () -> new MyService());
+ MyService mySecondService = (MyService) context.getBean("mySecondService");
+ assertTrue(mySecondService.getRandomNumber() < 10);
+ }
+
+ @Test
+ public void whenRegisterBeanWithCallback_thenOk() {
+ context.registerBean("myCallbackService", MyService.class, () -> new MyService(), bd -> bd.setAutowireCandidate(false));
+ MyService myCallbackService = (MyService) context.getBean("myCallbackService");
+ assertTrue(myCallbackService.getRandomNumber() < 10);
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/functional/ExploreSpring5URLPatternUsingRouterFunctionsTest.java b/spring-5-reactive/src/test/java/com/baeldung/functional/ExploreSpring5URLPatternUsingRouterFunctionsTest.java
new file mode 100644
index 0000000000..7a38fa697f
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/functional/ExploreSpring5URLPatternUsingRouterFunctionsTest.java
@@ -0,0 +1,110 @@
+package com.baeldung.functional;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+public class ExploreSpring5URLPatternUsingRouterFunctionsTest {
+
+ private static WebTestClient client;
+ private static WebServer server;
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ server = new ExploreSpring5URLPatternUsingRouterFunctions().start();
+ client = WebTestClient.bindToServer()
+ .baseUrl("http://localhost:" + server.getPort())
+ .build();
+ }
+
+ @AfterClass
+ public static void destroy() {
+ server.stop();
+ }
+
+ @Test
+ public void givenRouter_whenGetPathWithSingleCharWildcard_thenGotPathPattern() throws Exception {
+ client.get()
+ .uri("/paths")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("/p?ths");
+ }
+
+ @Test
+ public void givenRouter_whenMultipleURIVariablePattern_thenGotPathVariable() throws Exception {
+ client.get()
+ .uri("/test/ab/cd")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("/ab/cd");
+ }
+
+ @Test
+ public void givenRouter_whenGetMultipleCharWildcard_thenGotPathPattern() throws Exception {
+
+ client.get()
+ .uri("/wildcard")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("/*card path was accessed");
+ }
+
+ @Test
+ public void givenRouter_whenGetMultiplePathVaribleInSameSegment_thenGotPathVariables() throws Exception {
+
+ client.get()
+ .uri("/baeldung_tutorial")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("baeldung , tutorial");
+ }
+
+ @Test
+ public void givenRouter_whenGetRegexInPathVarible_thenGotPathVariable() throws Exception {
+
+ client.get()
+ .uri("/abcd")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("/{baeldung:[a-z]+} was accessed and baeldung=abcd");
+
+ client.get()
+ .uri("/1234")
+ .exchange()
+ .expectStatus()
+ .is4xxClientError();
+ }
+
+ @Test
+ public void givenResources_whenAccess_thenGot() throws Exception {
+ client.get()
+ .uri("/files/test/test.txt")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("test");
+
+ client.get()
+ .uri("/files/hello.txt")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("hello");
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java
new file mode 100644
index 0000000000..a7b951b930
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java
@@ -0,0 +1,141 @@
+package com.baeldung.functional;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.BodyInserters;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.BodyInserters.fromResource;
+
+public class FunctionalWebApplicationIntegrationTest {
+
+ private static WebTestClient client;
+ private static WebServer server;
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ server = new FunctionalWebApplication().start();
+ client = WebTestClient.bindToServer()
+ .baseUrl("http://localhost:" + server.getPort())
+ .build();
+ }
+
+ @AfterClass
+ public static void destroy() {
+ server.stop();
+ }
+
+ @Test
+ public void givenRouter_whenGetTest_thenGotHelloWorld() throws Exception {
+ client.get()
+ .uri("/test")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("helloworld");
+ }
+
+ @Test
+ public void givenIndexFilter_whenRequestRoot_thenRewrittenToTest() throws Exception {
+ client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("helloworld");
+ }
+
+ @Test
+ public void givenLoginForm_whenPostValidToken_thenSuccess() throws Exception {
+ MultiValueMap formData = new LinkedMultiValueMap<>(1);
+ formData.add("user", "baeldung");
+ formData.add("token", "you_know_what_to_do");
+
+ client.post()
+ .uri("/login")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .body(BodyInserters.fromFormData(formData))
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("welcome back!");
+ }
+
+ @Test
+ public void givenLoginForm_whenRequestWithInvalidToken_thenFail() throws Exception {
+ MultiValueMap formData = new LinkedMultiValueMap<>(2);
+ formData.add("user", "baeldung");
+ formData.add("token", "try_again");
+
+ client.post()
+ .uri("/login")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .body(BodyInserters.fromFormData(formData))
+ .exchange()
+ .expectStatus()
+ .isBadRequest();
+ }
+
+ @Test
+ public void givenUploadForm_whenRequestWithMultipartData_thenSuccess() throws Exception {
+ Resource resource = new ClassPathResource("/baeldung-weekly.png");
+ client.post()
+ .uri("/upload")
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .body(fromResource(resource))
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo(String.valueOf(resource.contentLength()));
+ }
+
+ @Test
+ public void givenActors_whenAddActor_thenAdded() throws Exception {
+ client.get()
+ .uri("/actor")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBodyList(Actor.class)
+ .hasSize(2);
+
+ client.post()
+ .uri("/actor")
+ .body(fromObject(new Actor("Clint", "Eastwood")))
+ .exchange()
+ .expectStatus()
+ .isOk();
+
+ client.get()
+ .uri("/actor")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBodyList(Actor.class)
+ .hasSize(3);
+ }
+
+ @Test
+ public void givenResources_whenAccess_thenGot() throws Exception {
+ client.get()
+ .uri("/files/hello.txt")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("hello");
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jsonb/JsonbIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jsonb/JsonbIntegrationTest.java
new file mode 100644
index 0000000000..756b303f3b
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jsonb/JsonbIntegrationTest.java
@@ -0,0 +1,43 @@
+package com.baeldung.jsonb;
+
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Spring5Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class JsonbIntegrationTest {
+ @Value("${security.user.name}")
+ private String username;
+ @Value("${security.user.password}")
+ private String password;
+
+ @Autowired
+ private TestRestTemplate template;
+
+ @Test
+ public void givenId_whenUriIsPerson_thenGetPerson() {
+ ResponseEntity response = template.withBasicAuth(username, password)
+ .getForEntity("/person/1", Person.class);
+ Person person = response.getBody();
+ assertTrue(person.equals(new Person(2, "Jhon", "jhon1@test.com", 0, LocalDate.of(2019, 9, 9), BigDecimal.valueOf(1500.0))));
+ }
+
+ @Test
+ public void whenSendPostAPerson_thenGetOkStatus() {
+ ResponseEntity response = template.withBasicAuth(username, password)
+ .postForEntity("/person", "{\"birthDate\":\"07-09-2017\",\"email\":\"jhon1@test.com\",\"person-name\":\"Jhon\",\"id\":10}", Boolean.class);
+ assertTrue(response.getBody());
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/EnabledOnJava8.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/EnabledOnJava8.java
new file mode 100644
index 0000000000..c6d3b7ff10
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/EnabledOnJava8.java
@@ -0,0 +1,18 @@
+package com.baeldung.jupiter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.test.context.junit.jupiter.EnabledIf;
+
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@EnabledIf(
+ expression = "#{systemProperties['java.version'].startsWith('1.8')}",
+ reason = "Enabled on Java 8"
+)
+public @interface EnabledOnJava8 {
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5EnabledAnnotationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5EnabledAnnotationTest.java
new file mode 100644
index 0000000000..ae058bc8ba
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5EnabledAnnotationTest.java
@@ -0,0 +1,50 @@
+package com.baeldung.jupiter;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.DisabledIf;
+import org.springframework.test.context.junit.jupiter.EnabledIf;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+@SpringJUnitConfig(Spring5EnabledAnnotationTest.Config.class)
+@TestPropertySource(properties = { "tests.enabled=true" })
+public class Spring5EnabledAnnotationTest {
+
+ @Configuration
+ static class Config {
+ }
+
+ @EnabledIf("true")
+ @Test
+ void givenEnabledIfLiteral_WhenTrue_ThenTestExecuted() {
+ assertTrue(true);
+ }
+
+ @EnabledIf(expression = "${tests.enabled}", loadContext = true)
+ @Test
+ void givenEnabledIfExpression_WhenTrue_ThenTestExecuted() {
+ assertTrue(true);
+ }
+
+ @EnabledIf("#{systemProperties['java.version'].startsWith('1.8')}")
+ @Test
+ void givenEnabledIfSpel_WhenTrue_ThenTestExecuted() {
+ assertTrue(true);
+ }
+
+ @EnabledOnJava8
+ @Test
+ void givenEnabledOnJava8_WhenTrue_ThenTestExecuted() {
+ assertTrue(true);
+ }
+
+ @DisabledIf("#{systemProperties['java.version'].startsWith('1.7')}")
+ @Test
+ void givenDisabledIf_WhenTrue_ThenTestNotExecuted() {
+ assertTrue(true);
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5ComposedAnnotationIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5ComposedAnnotationIntegrationTest.java
new file mode 100644
index 0000000000..42d27b90f4
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5ComposedAnnotationIntegrationTest.java
@@ -0,0 +1,38 @@
+package com.baeldung.jupiter;
+
+import com.baeldung.web.reactive.Task;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@SpringJUnit5Config(TestConfig.class)
+@DisplayName("@SpringJUnit5Config Tests")
+class Spring5JUnit5ComposedAnnotationIntegrationTest {
+
+ @Autowired
+ private Task task;
+
+ @Autowired
+ private List tasks;
+
+ @Test
+ @DisplayName("ApplicationContext injected into method")
+ void givenAMethodName_whenInjecting_thenApplicationContextInjectedIntoMethod(ApplicationContext applicationContext) {
+ assertNotNull(applicationContext, "ApplicationContext should have been injected into method by Spring");
+ assertEquals(this.task, applicationContext.getBean("taskName", Task.class));
+ }
+
+ @Test
+ @DisplayName("Spring @Beans injected into fields")
+ void givenAnObject_whenInjecting_thenSpringBeansInjected() {
+ assertNotNull(task, "Task should have been @Autowired by Spring");
+ assertEquals("taskName", task.getName(), "Task's name");
+ assertEquals(1, tasks.size(), "Number of Tasks in context");
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5IntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5IntegrationTest.java
new file mode 100644
index 0000000000..0f00a85832
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5IntegrationTest.java
@@ -0,0 +1,25 @@
+package com.baeldung.jupiter;
+
+import com.baeldung.web.reactive.Task;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = TestConfig.class)
+class Spring5JUnit5IntegrationTest {
+
+ @Autowired
+ private Task task;
+
+ @Test
+ void givenAMethodName_whenInjecting_thenApplicationContextInjectedIntoMetho(ApplicationContext applicationContext) {
+ assertNotNull(applicationContext, "ApplicationContext should have been injected by Spring");
+ assertEquals(this.task, applicationContext.getBean("taskName", Task.class));
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5ParallelIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5ParallelIntegrationTest.java
new file mode 100644
index 0000000000..55b0fcf267
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5JUnit5ParallelIntegrationTest.java
@@ -0,0 +1,25 @@
+package com.baeldung.jupiter;
+
+import com.baeldung.Example1IntegrationTest;
+import com.baeldung.Example2IntegrationTest;
+import org.junit.experimental.ParallelComputer;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.Computer;
+import org.junit.runner.JUnitCore;
+
+class Spring5JUnit5ParallelIntegrationTest {
+
+ @Test
+ void givenTwoTestClasses_whenJUnitRunParallel_thenTheTestsExecutingParallel() {
+ final Class>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
+
+ JUnitCore.runClasses(new ParallelComputer(true, true), classes);
+ }
+
+ @Test
+ void givenTwoTestClasses_whenJUnitRunParallel_thenTheTestsExecutingLinear() {
+ final Class>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
+
+ JUnitCore.runClasses(new Computer(), classes);
+ }
+}
\ No newline at end of file
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5Java8NewFeaturesIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5Java8NewFeaturesIntegrationTest.java
new file mode 100644
index 0000000000..f58bf9f3cd
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5Java8NewFeaturesIntegrationTest.java
@@ -0,0 +1,31 @@
+package com.baeldung.jupiter;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class Spring5Java8NewFeaturesIntegrationTest {
+
+ @FunctionalInterface
+ public interface FunctionalInterfaceExample {
+ Result reverseString(Input input);
+ }
+
+ public class StringUtils {
+ FunctionalInterfaceExample functionLambdaString = s -> Pattern.compile(" +")
+ .splitAsStream(s)
+ .map(word -> new StringBuilder(word).reverse())
+ .collect(Collectors.joining(" "));
+ }
+
+ @Test
+ void givenStringUtil_whenSupplierCall_thenFunctionalInterfaceReverseString() throws Exception {
+ Supplier stringUtilsSupplier = StringUtils::new;
+
+ assertEquals(stringUtilsSupplier.get().functionLambdaString.reverseString("hello"), "olleh");
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5ReactiveServerClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5ReactiveServerClientIntegrationTest.java
new file mode 100644
index 0000000000..bbd852d625
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/Spring5ReactiveServerClientIntegrationTest.java
@@ -0,0 +1,94 @@
+package com.baeldung.jupiter;
+
+import com.baeldung.web.reactive.Task;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.ipc.netty.NettyContext;
+import reactor.ipc.netty.http.server.HttpServer;
+
+import java.time.Duration;
+
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
+
+public class Spring5ReactiveServerClientIntegrationTest {
+
+ private static NettyContext nettyContext;
+
+ @BeforeAll
+ public static void setUp() throws Exception {
+ HttpServer server = HttpServer.create("localhost", 8080);
+ RouterFunction> route = RouterFunctions.route(POST("/task/process"), request -> ServerResponse.ok()
+ .body(request.bodyToFlux(Task.class)
+ .map(ll -> new Task("TaskName", 1)), Task.class))
+ .and(RouterFunctions.route(GET("/task"), request -> ServerResponse.ok()
+ .body(Mono.just("server is alive"), String.class)));
+ HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
+ ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
+ nettyContext = server.newHandler(adapter)
+ .block();
+ }
+
+ @AfterAll
+ public static void shutDown() {
+ nettyContext.dispose();
+ }
+
+ // @Test
+ // public void givenCheckTask_whenServerHandle_thenServerResponseALiveString() throws Exception {
+ // WebClient client = WebClient.create("http://localhost:8080");
+ // Mono result = client
+ // .get()
+ // .uri("/task")
+ // .exchange()
+ // .then(response -> response.bodyToMono(String.class));
+ //
+ // assertThat(result.block()).isInstanceOf(String.class);
+ // }
+
+ // @Test
+ // public void givenThreeTasks_whenServerHandleTheTasks_thenServerResponseATask() throws Exception {
+ // URI uri = URI.create("http://localhost:8080/task/process");
+ // ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());
+ // ClientRequest request = ClientRequest
+ // .method(HttpMethod.POST, uri)
+ // .body(BodyInserters.fromPublisher(getLatLngs(), Task.class))
+ // .build();
+ //
+ // Flux taskResponse = exchange
+ // .exchange(request)
+ // .flatMap(response -> response.bodyToFlux(Task.class));
+ //
+ // assertThat(taskResponse.blockFirst()).isInstanceOf(Task.class);
+ // }
+
+ // @Test
+ // public void givenCheckTask_whenServerHandle_thenOragicServerResponseALiveString() throws Exception {
+ // URI uri = URI.create("http://localhost:8080/task");
+ // ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());
+ // ClientRequest request = ClientRequest
+ // .method(HttpMethod.GET, uri)
+ // .body(BodyInserters.fromPublisher(getLatLngs(), Task.class))
+ // .build();
+ //
+ // Flux taskResponse = exchange
+ // .exchange(request)
+ // .flatMap(response -> response.bodyToFlux(String.class));
+ //
+ // assertThat(taskResponse.blockFirst()).isInstanceOf(String.class);
+ // }
+
+ private static Flux getLatLngs() {
+ return Flux.range(0, 3)
+ .zipWith(Flux.interval(Duration.ofSeconds(1)))
+ .map(x -> new Task("taskname", 1))
+ .doOnNext(ll -> System.out.println("Produced: {}" + ll));
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/SpringJUnitConfigTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/SpringJUnitConfigTest.java
new file mode 100644
index 0000000000..6b0a6f9808
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/SpringJUnitConfigTest.java
@@ -0,0 +1,33 @@
+package com.baeldung.jupiter;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+/**
+ * @SpringJUnitConfig(SpringJUnitConfigTest.Config.class) is equivalent to:
+ *
+ * @ExtendWith(SpringExtension.class)
+ * @ContextConfiguration(classes = SpringJUnitConfigTest.Config.class )
+ *
+ */
+@SpringJUnitConfig(SpringJUnitConfigTest.Config.class)
+public class SpringJUnitConfigTest {
+
+ @Configuration
+ static class Config {
+ }
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ void givenAppContext_WhenInjected_ThenItShouldNotBeNull() {
+ assertNotNull(applicationContext);
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/jupiter/SpringJUnitWebConfigTest.java b/spring-5-reactive/src/test/java/com/baeldung/jupiter/SpringJUnitWebConfigTest.java
new file mode 100644
index 0000000000..c679dce77f
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/jupiter/SpringJUnitWebConfigTest.java
@@ -0,0 +1,34 @@
+package com.baeldung.jupiter;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * @SpringJUnitWebConfig(SpringJUnitWebConfigTest.Config.class) is equivalent to:
+ *
+ * @ExtendWith(SpringExtension.class)
+ * @WebAppConfiguration
+ * @ContextConfiguration(classes = SpringJUnitWebConfigTest.Config.class )
+ *
+ */
+@SpringJUnitWebConfig(SpringJUnitWebConfigTest.Config.class)
+public class SpringJUnitWebConfigTest {
+
+ @Configuration
+ static class Config {
+ }
+
+ @Autowired
+ private WebApplicationContext webAppContext;
+
+ @Test
+ void givenWebAppContext_WhenInjected_ThenItShouldNotBeNull() {
+ assertNotNull(webAppContext);
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/security/SecurityTest.java b/spring-5-reactive/src/test/java/com/baeldung/security/SecurityTest.java
new file mode 100644
index 0000000000..a1c940a17a
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/security/SecurityTest.java
@@ -0,0 +1,48 @@
+package com.baeldung.security;
+
+import com.baeldung.SpringSecurity5Application;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = SpringSecurity5Application.class)
+public class SecurityTest {
+
+ @Autowired
+ ApplicationContext context;
+
+ private WebTestClient rest;
+
+ @Before
+ public void setup() {
+ this.rest = WebTestClient
+ .bindToApplicationContext(this.context)
+ .configureClient()
+ .build();
+ }
+
+ @Test
+ public void whenNoCredentials_thenRedirectToLogin() {
+ this.rest.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().is3xxRedirection();
+ }
+
+ @Test
+ @WithMockUser
+ public void whenHasCredentials_thenSeesGreeting() {
+ this.rest.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo("Hello, user");
+ }
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/PathPatternsUsingHandlerMethodIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/PathPatternsUsingHandlerMethodIntegrationTest.java
new file mode 100644
index 0000000000..c2ed8ff071
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/web/PathPatternsUsingHandlerMethodIntegrationTest.java
@@ -0,0 +1,101 @@
+package com.baeldung.web;
+
+import com.baeldung.Spring5Application;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Spring5Application.class)
+public class PathPatternsUsingHandlerMethodIntegrationTest {
+
+ private static WebTestClient client;
+
+ @BeforeClass
+ public static void setUp() {
+ client = WebTestClient.bindToController(new PathPatternController())
+ .build();
+ }
+
+ @Test
+ public void givenHandlerMethod_whenMultipleURIVariablePattern_then200() {
+
+ client.get()
+ .uri("/spring5/ab/cd")
+ .exchange()
+ .expectStatus()
+ .is2xxSuccessful()
+ .expectBody()
+ .equals("/ab/cd");
+ }
+
+ @Test
+ public void givenHandlerMethod_whenURLWithWildcardTakingZeroOrMoreChar_then200() {
+
+ client.get()
+ .uri("/spring5/userid")
+ .exchange()
+ .expectStatus()
+ .is2xxSuccessful()
+ .expectBody()
+ .equals("/spring5/*id");
+ }
+
+ @Test
+ public void givenHandlerMethod_whenURLWithWildcardTakingExactlyOneChar_then200() {
+
+ client.get()
+ .uri("/string5")
+ .exchange()
+ .expectStatus()
+ .is2xxSuccessful()
+ .expectBody()
+ .equals("/s?ring5");
+ }
+
+ @Test
+ public void givenHandlerMethod_whenURLWithWildcardTakingZeroOrMorePathSegments_then200() {
+
+ client.get()
+ .uri("/resources/baeldung")
+ .exchange()
+ .expectStatus()
+ .is2xxSuccessful()
+ .expectBody()
+ .equals("/resources/**");
+ }
+
+ @Test
+ public void givenHandlerMethod_whenURLWithRegexInPathVariable_thenExpectedOutput() {
+
+ client.get()
+ .uri("/abc")
+ .exchange()
+ .expectStatus()
+ .is2xxSuccessful()
+ .expectBody()
+ .equals("abc");
+
+ client.get()
+ .uri("/123")
+ .exchange()
+ .expectStatus()
+ .is4xxClientError();
+ }
+
+ @Test
+ public void givenHandlerMethod_whenURLWithMultiplePathVariablesInSameSegment_then200() {
+
+ client.get()
+ .uri("/baeldung_tutorial")
+ .exchange()
+ .expectStatus()
+ .is2xxSuccessful()
+ .expectBody()
+ .equals("Two variables are var1=baeldung and var2=tutorial");
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebTestClientTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebTestClientTest.java
new file mode 100644
index 0000000000..43114b5b50
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebTestClientTest.java
@@ -0,0 +1,60 @@
+package com.baeldung.web.client;
+
+import com.baeldung.Spring5Application;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.reactive.function.server.RequestPredicates;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.WebHandler;
+import reactor.core.publisher.Mono;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Spring5Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class WebTestClientTest {
+
+ @LocalServerPort
+ private int port;
+
+ private final RouterFunction ROUTER_FUNCTION = RouterFunctions.route(RequestPredicates.GET("/resource"), request -> ServerResponse.ok()
+ .build());
+ private final WebHandler WEB_HANDLER = exchange -> Mono.empty();
+
+ @Test
+ public void testWebTestClientWithServerWebHandler() {
+ WebTestClient.bindToWebHandler(WEB_HANDLER)
+ .build();
+ }
+
+ @Test
+ public void testWebTestClientWithRouterFunction() {
+ WebTestClient.bindToRouterFunction(ROUTER_FUNCTION)
+ .build()
+ .get()
+ .uri("/resource")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody()
+ .isEmpty();
+ }
+
+ @Test
+ public void testWebTestClientWithServerURL() {
+ WebTestClient.bindToServer()
+ .baseUrl("http://localhost:" + port)
+ .build()
+ .get()
+ .uri("/resource")
+ .exchange()
+ .expectStatus()
+ .is3xxRedirection()
+ .expectBody();
+ }
+
+}
diff --git a/spring-5-reactive/src/test/resources/baeldung-weekly.png b/spring-5-reactive/src/test/resources/baeldung-weekly.png
new file mode 100644
index 0000000000..5a27d61dae
Binary files /dev/null and b/spring-5-reactive/src/test/resources/baeldung-weekly.png differ
diff --git a/spring-5/src/main/java/com/baeldung/web/PathPatternController.java b/spring-5/src/main/java/com/baeldung/web/PathPatternController.java
index 15b689257a..6fd972f2a4 100644
--- a/spring-5/src/main/java/com/baeldung/web/PathPatternController.java
+++ b/spring-5/src/main/java/com/baeldung/web/PathPatternController.java
@@ -2,7 +2,6 @@ package com.baeldung.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController