diff --git a/pom.xml b/pom.xml
index cf9de13ba4..f8e1910b9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,7 @@
testing-modules/selenium-junit-testng
persistence-modules/solr
spark-java
+ spring-4
spring-5
spring-5-reactive
spring-5-mvc
diff --git a/spring-4/README.md b/spring-4/README.md
new file mode 100644
index 0000000000..0c62173b6a
--- /dev/null
+++ b/spring-4/README.md
@@ -0,0 +1,2 @@
+### Relevant Articles:
+- [Guide to Flips For Spring](http://www.baeldung.com/guide-to-flips-for-spring/)
diff --git a/spring-4/pom.xml b/spring-4/pom.xml
new file mode 100644
index 0000000000..8a57888e56
--- /dev/null
+++ b/spring-4/pom.xml
@@ -0,0 +1,62 @@
+
+ 4.0.0
+
+ spring-4
+ spring-4
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-4
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 1.5.10.RELEASE
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 1.5.9.RELEASE
+
+
+
+ com.github.feature-flip
+ flips-web
+ 1.0.1
+
+
+
+ org.projectlombok
+ lombok
+
+ 1.16.18
+ provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
diff --git a/spring-4/src/main/java/com/baeldung/flips/ApplicationConfig.java b/spring-4/src/main/java/com/baeldung/flips/ApplicationConfig.java
new file mode 100644
index 0000000000..7001aeb991
--- /dev/null
+++ b/spring-4/src/main/java/com/baeldung/flips/ApplicationConfig.java
@@ -0,0 +1,15 @@
+package com.baeldung.flips;
+
+import org.flips.describe.config.FlipWebContextConfiguration;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+
+@SpringBootApplication
+@Import(FlipWebContextConfiguration.class)
+public class ApplicationConfig {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ApplicationConfig.class, args);
+ }
+}
\ No newline at end of file
diff --git a/spring-4/src/main/java/com/baeldung/flips/controller/FlipController.java b/spring-4/src/main/java/com/baeldung/flips/controller/FlipController.java
new file mode 100644
index 0000000000..50458023b3
--- /dev/null
+++ b/spring-4/src/main/java/com/baeldung/flips/controller/FlipController.java
@@ -0,0 +1,65 @@
+package com.baeldung.flips.controller;
+
+import com.baeldung.flips.model.Foo;
+import com.baeldung.flips.service.FlipService;
+import org.flips.annotation.FlipOnDateTime;
+import org.flips.annotation.FlipOnDaysOfWeek;
+import org.flips.annotation.FlipOnEnvironmentProperty;
+import org.flips.annotation.FlipOnProfiles;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.DayOfWeek;
+import java.util.List;
+
+@RestController
+public class FlipController {
+
+ private FlipService flipService;
+
+ @Autowired
+ public FlipController(FlipService flipService) {
+ this.flipService = flipService;
+ }
+
+ @RequestMapping(value = "/foos", method = RequestMethod.GET)
+ @FlipOnProfiles(activeProfiles = "dev")
+ public List getAllFoos() {
+ return flipService.getAllFoos();
+ }
+
+ @RequestMapping(value = "/foo/{id}", method = RequestMethod.GET)
+ @FlipOnDaysOfWeek(daysOfWeek = {
+ DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY,
+ DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY
+ })
+ public Foo getFooByNewId(@PathVariable int id) {
+ return flipService.getFooById(id).orElse(new Foo("Not Found", -1));
+ }
+
+ @RequestMapping(value = "/foo/last", method = RequestMethod.GET)
+ @FlipOnDateTime(cutoffDateTimeProperty = "last.active.after")
+ public Foo getLastFoo() {
+ return flipService.getLastFoo();
+ }
+
+ @RequestMapping(value = "/foo/first", method = RequestMethod.GET)
+ @FlipOnDateTime(cutoffDateTimeProperty = "first.active.after")
+ public Foo getFirstFoo() {
+ return flipService.getLastFoo();
+ }
+
+ @RequestMapping(value = "/foos/{id}", method = RequestMethod.GET)
+ @FlipOnEnvironmentProperty(property = "feature.foo.by.id", expectedValue = "Y")
+ public Foo getFooById(@PathVariable int id) {
+ return flipService.getFooById(id).orElse(new Foo("Not Found", -1));
+ }
+
+ @RequestMapping(value = "/foo/new", method = RequestMethod.GET)
+ public Foo getNewThing() {
+ return flipService.getNewFoo();
+ }
+}
\ No newline at end of file
diff --git a/spring-4/src/main/java/com/baeldung/flips/model/Foo.java b/spring-4/src/main/java/com/baeldung/flips/model/Foo.java
new file mode 100644
index 0000000000..d98abb79a9
--- /dev/null
+++ b/spring-4/src/main/java/com/baeldung/flips/model/Foo.java
@@ -0,0 +1,11 @@
+package com.baeldung.flips.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@RequiredArgsConstructor
+public class Foo {
+ private final String name;
+ private final int id;
+}
diff --git a/spring-4/src/main/java/com/baeldung/flips/service/FlipService.java b/spring-4/src/main/java/com/baeldung/flips/service/FlipService.java
new file mode 100644
index 0000000000..9f7fb325a5
--- /dev/null
+++ b/spring-4/src/main/java/com/baeldung/flips/service/FlipService.java
@@ -0,0 +1,50 @@
+package com.baeldung.flips.service;
+
+import com.baeldung.flips.model.Foo;
+import org.flips.annotation.FlipBean;
+import org.flips.annotation.FlipOnSpringExpression;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class FlipService {
+
+ private final List foos;
+
+ public FlipService() {
+ foos = new ArrayList<>();
+ foos.add(new Foo("Foo1", 1));
+ foos.add(new Foo("Foo2", 2));
+ foos.add(new Foo("Foo3", 3));
+ foos.add(new Foo("Foo4", 4));
+ foos.add(new Foo("Foo5", 5));
+ foos.add(new Foo("Foo6", 6));
+
+ }
+
+ public List getAllFoos() {
+ return foos;
+ }
+
+ public Optional getFooById(int id) {
+ return foos.stream().filter(foo -> (foo.getId() == id)).findFirst();
+ }
+
+ @FlipBean(with = NewFlipService.class)
+ @FlipOnSpringExpression(expression = "(2 + 2) == 4")
+ public Foo getNewFoo() {
+ return new Foo("New Foo!", 99);
+ }
+
+ public Foo getLastFoo() {
+ return foos.get(foos.size() - 1);
+ }
+
+ public Foo getFirstFoo() {
+ return foos.get(0);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-4/src/main/java/com/baeldung/flips/service/NewFlipService.java b/spring-4/src/main/java/com/baeldung/flips/service/NewFlipService.java
new file mode 100644
index 0000000000..1dcda9b6ca
--- /dev/null
+++ b/spring-4/src/main/java/com/baeldung/flips/service/NewFlipService.java
@@ -0,0 +1,13 @@
+package com.baeldung.flips.service;
+
+import com.baeldung.flips.model.Foo;
+import org.springframework.stereotype.Service;
+
+@Service
+public class NewFlipService {
+
+ public Foo getNewFoo() {
+ return new Foo("Shiny New Foo!", 100);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-4/src/main/resources/application.properties b/spring-4/src/main/resources/application.properties
new file mode 100644
index 0000000000..274896be15
--- /dev/null
+++ b/spring-4/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+feature.foo.by.id=Y
+feature.new.foo=Y
+last.active.after=2018-03-14T00:00:00Z
+first.active.after=2999-03-15T00:00:00Z
+logging.level.org.flips=info
\ No newline at end of file
diff --git a/spring-4/src/test/java/com/baeldung/flips/controller/FlipControllerTest.java b/spring-4/src/test/java/com/baeldung/flips/controller/FlipControllerTest.java
new file mode 100644
index 0000000000..1b8c78e2a4
--- /dev/null
+++ b/spring-4/src/test/java/com/baeldung/flips/controller/FlipControllerTest.java
@@ -0,0 +1,74 @@
+package com.baeldung.flips.controller;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(properties = {
+ "feature.foo.by.id=Y",
+ "feature.new.foo=Y",
+ "last.active.after=2018-03-14T00:00:00Z",
+ "first.active.after=2999-03-15T00:00:00Z",
+ "logging.level.org.flips=info"
+
+})
+@AutoConfigureMockMvc
+@ActiveProfiles("dev")
+public class FlipControllerTest {
+
+ @Autowired private MockMvc mvc;
+
+ @Test
+ public void givenValidDayOfWeek_APIAvailable() throws Exception {
+ mvc.perform(MockMvcRequestBuilders.get("/foo/1"))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo("Foo1")))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(1)));
+ }
+
+ @Test
+ public void givenValidDate_APIAvailable() throws Exception {
+ mvc.perform(MockMvcRequestBuilders.get("/foo/last"))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo("Foo6")))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(6)));
+ }
+
+ @Test
+ public void givenInvalidDate_APINotAvailable() throws Exception {
+ mvc.perform(MockMvcRequestBuilders.get("/foo/first"))
+ .andExpect(MockMvcResultMatchers.status().is(501));
+ }
+
+ @Test
+ public void givenCorrectProfile_APIAvailable() throws Exception {
+ mvc.perform(MockMvcRequestBuilders.get("/foos"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(6)));
+ }
+
+ @Test
+ public void givenPropertySet_APIAvailable() throws Exception {
+ mvc.perform(MockMvcRequestBuilders.get("/foos/1"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo("Foo1")))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(1)));
+ }
+
+ @Test
+ public void getValidExpression_FlipBean() throws Exception {
+ mvc.perform(MockMvcRequestBuilders.get("/foo/new"))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo("Shiny New Foo!")))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(100)));
+ }
+}
\ No newline at end of file