diff --git a/core-java-concurrency/src/main/java/com/baeldung/concurrent/countdownlatch/CountdownLatchCountExample.java b/core-java-concurrency/src/main/java/com/baeldung/concurrent/countdownlatch/CountdownLatchCountExample.java
new file mode 100644
index 0000000000..08c7eeec03
--- /dev/null
+++ b/core-java-concurrency/src/main/java/com/baeldung/concurrent/countdownlatch/CountdownLatchCountExample.java
@@ -0,0 +1,33 @@
+package com.baeldung.concurrent.countdownlatch;
+
+import java.util.concurrent.CountDownLatch;
+
+public class CountdownLatchCountExample {
+
+ private int count;
+
+ public CountdownLatchCountExample(int count) {
+ this.count = count;
+ }
+
+ public boolean callTwiceInSameThread() {
+ CountDownLatch countDownLatch = new CountDownLatch(count);
+ Thread t = new Thread(() -> {
+ countDownLatch.countDown();
+ countDownLatch.countDown();
+ });
+ t.start();
+
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return countDownLatch.getCount() == 0;
+ }
+
+ public static void main(String[] args) {
+ CountdownLatchCountExample ex = new CountdownLatchCountExample(2);
+ System.out.println("Is CountDown Completed : " + ex.callTwiceInSameThread());
+ }
+}
diff --git a/core-java-concurrency/src/main/java/com/baeldung/concurrent/countdownlatch/CountdownLatchResetExample.java b/core-java-concurrency/src/main/java/com/baeldung/concurrent/countdownlatch/CountdownLatchResetExample.java
new file mode 100644
index 0000000000..1828b7f91e
--- /dev/null
+++ b/core-java-concurrency/src/main/java/com/baeldung/concurrent/countdownlatch/CountdownLatchResetExample.java
@@ -0,0 +1,41 @@
+package com.baeldung.concurrent.countdownlatch;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CountdownLatchResetExample {
+
+ private int count;
+ private int threadCount;
+ private final AtomicInteger updateCount;
+
+ CountdownLatchResetExample(int count, int threadCount) {
+ updateCount = new AtomicInteger(0);
+ this.count = count;
+ this.threadCount = threadCount;
+ }
+
+ public int countWaits() {
+ CountDownLatch countDownLatch = new CountDownLatch(count);
+ ExecutorService es = Executors.newFixedThreadPool(threadCount);
+ for (int i = 0; i < threadCount; i++) {
+ es.execute(() -> {
+ long prevValue = countDownLatch.getCount();
+ countDownLatch.countDown();
+ if (countDownLatch.getCount() != prevValue) {
+ updateCount.incrementAndGet();
+ }
+ });
+ }
+
+ es.shutdown();
+ return updateCount.get();
+ }
+
+ public static void main(String[] args) {
+ CountdownLatchResetExample ex = new CountdownLatchResetExample(5, 20);
+ System.out.println("Count : " + ex.countWaits());
+ }
+}
diff --git a/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCompletionMethodExample.java b/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCompletionMethodExample.java
new file mode 100644
index 0000000000..7c1299da62
--- /dev/null
+++ b/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCompletionMethodExample.java
@@ -0,0 +1,45 @@
+package com.baeldung.concurrent.cyclicbarrier;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CyclicBarrierCompletionMethodExample {
+
+ private int count;
+ private int threadCount;
+ private final AtomicInteger updateCount;
+
+ CyclicBarrierCompletionMethodExample(int count, int threadCount) {
+ updateCount = new AtomicInteger(0);
+ this.count = count;
+ this.threadCount = threadCount;
+ }
+
+ public int countTrips() {
+
+ CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> {
+ updateCount.incrementAndGet();
+ });
+
+ ExecutorService es = Executors.newFixedThreadPool(threadCount);
+ for (int i = 0; i < threadCount; i++) {
+ es.execute(() -> {
+ try {
+ cyclicBarrier.await();
+ } catch (InterruptedException | BrokenBarrierException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ es.shutdown();
+ return updateCount.get();
+ }
+
+ public static void main(String[] args) {
+ CyclicBarrierCompletionMethodExample ex = new CyclicBarrierCompletionMethodExample(5, 20);
+ System.out.println("Count : " + ex.countTrips());
+ }
+}
diff --git a/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCountExample.java b/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCountExample.java
new file mode 100644
index 0000000000..9d637b428b
--- /dev/null
+++ b/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCountExample.java
@@ -0,0 +1,32 @@
+package com.baeldung.concurrent.cyclicbarrier;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+public class CyclicBarrierCountExample {
+
+ private int count;
+
+ public CyclicBarrierCountExample(int count) {
+ this.count = count;
+ }
+
+ public boolean callTwiceInSameThread() {
+ CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
+ Thread t = new Thread(() -> {
+ try {
+ cyclicBarrier.await();
+ cyclicBarrier.await();
+ } catch (InterruptedException | BrokenBarrierException e) {
+ e.printStackTrace();
+ }
+ });
+ t.start();
+ return cyclicBarrier.isBroken();
+ }
+
+ public static void main(String[] args) {
+ CyclicBarrierCountExample ex = new CyclicBarrierCountExample(7);
+ System.out.println("Count : " + ex.callTwiceInSameThread());
+ }
+}
diff --git a/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierResetExample.java b/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierResetExample.java
new file mode 100644
index 0000000000..76b6198bc4
--- /dev/null
+++ b/core-java-concurrency/src/main/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierResetExample.java
@@ -0,0 +1,46 @@
+package com.baeldung.concurrent.cyclicbarrier;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CyclicBarrierResetExample {
+
+ private int count;
+ private int threadCount;
+ private final AtomicInteger updateCount;
+
+ CyclicBarrierResetExample(int count, int threadCount) {
+ updateCount = new AtomicInteger(0);
+ this.count = count;
+ this.threadCount = threadCount;
+ }
+
+ public int countWaits() {
+
+ CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
+
+ ExecutorService es = Executors.newFixedThreadPool(threadCount);
+ for (int i = 0; i < threadCount; i++) {
+ es.execute(() -> {
+ try {
+ if (cyclicBarrier.getNumberWaiting() > 0) {
+ updateCount.incrementAndGet();
+ }
+ cyclicBarrier.await();
+ } catch (InterruptedException | BrokenBarrierException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ es.shutdown();
+ return updateCount.get();
+ }
+
+ public static void main(String[] args) {
+ CyclicBarrierResetExample ex = new CyclicBarrierResetExample(7, 20);
+ System.out.println("Count : " + ex.countWaits());
+ }
+}
diff --git a/core-java-concurrency/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchCountExampleUnitTest.java b/core-java-concurrency/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchCountExampleUnitTest.java
new file mode 100644
index 0000000000..835efa53f2
--- /dev/null
+++ b/core-java-concurrency/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchCountExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.concurrent.countdownlatch;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class CountdownLatchCountExampleUnitTest {
+
+ @Test
+ public void whenCountDownLatch_completed() {
+ CountdownLatchCountExample ex = new CountdownLatchCountExample(2);
+ boolean isCompleted = ex.callTwiceInSameThread();
+ assertTrue(isCompleted);
+ }
+}
diff --git a/core-java-concurrency/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchResetExampleUnitTest.java b/core-java-concurrency/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchResetExampleUnitTest.java
new file mode 100644
index 0000000000..d2d43f6312
--- /dev/null
+++ b/core-java-concurrency/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchResetExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.concurrent.countdownlatch;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class CountdownLatchResetExampleUnitTest {
+
+ @Test
+ public void whenCountDownLatch_noReset() {
+ CountdownLatchResetExample ex = new CountdownLatchResetExample(7,20);
+ int lineCount = ex.countWaits();
+ assertTrue(lineCount <= 7);
+ }
+}
diff --git a/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCompletionMethodExampleUnitTest.java b/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCompletionMethodExampleUnitTest.java
new file mode 100644
index 0000000000..310063c86c
--- /dev/null
+++ b/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCompletionMethodExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.concurrent.cyclicbarrier;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class CyclicBarrierCompletionMethodExampleUnitTest {
+
+ @Test
+ public void whenCyclicBarrier_countTrips() {
+ CyclicBarrierCompletionMethodExample ex = new CyclicBarrierCompletionMethodExample(7,20);
+ int lineCount = ex.countTrips();
+ assertEquals(2, lineCount);
+ }
+}
diff --git a/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCountExampleUnitTest.java b/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCountExampleUnitTest.java
new file mode 100644
index 0000000000..9b7f3d9945
--- /dev/null
+++ b/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierCountExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.concurrent.cyclicbarrier;
+
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+public class CyclicBarrierCountExampleUnitTest {
+
+ @Test
+ public void whenCyclicBarrier_notCompleted() {
+ CyclicBarrierCountExample ex = new CyclicBarrierCountExample(2);
+ boolean isCompleted = ex.callTwiceInSameThread();
+ assertFalse(isCompleted);
+ }
+}
diff --git a/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierResetExampleUnitTest.java b/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierResetExampleUnitTest.java
new file mode 100644
index 0000000000..8d2b148f06
--- /dev/null
+++ b/core-java-concurrency/src/test/java/com/baeldung/concurrent/cyclicbarrier/CyclicBarrierResetExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.concurrent.cyclicbarrier;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class CyclicBarrierResetExampleUnitTest {
+
+ @Test
+ public void whenCyclicBarrier_reset() {
+ CyclicBarrierResetExample ex = new CyclicBarrierResetExample(7,20);
+ int lineCount = ex.countWaits();
+ assertTrue(lineCount > 7);
+ }
+}
diff --git a/core-java-lang/pom.xml b/core-java-lang/pom.xml
index ace39de274..2f307859f1 100644
--- a/core-java-lang/pom.xml
+++ b/core-java-lang/pom.xml
@@ -66,6 +66,12 @@
mail
${javax.mail.version}
+
+ nl.jqno.equalsverifier
+ equalsverifier
+ ${equalsverifier.version}
+ test
+
@@ -424,6 +430,7 @@
3.1.1
2.0.3.RELEASE
1.6.0
+ 3.0.3
diff --git a/core-java-lang/src/main/java/com/baeldung/equalshashcode/Money.java b/core-java-lang/src/main/java/com/baeldung/equalshashcode/Money.java
new file mode 100644
index 0000000000..60c043545d
--- /dev/null
+++ b/core-java-lang/src/main/java/com/baeldung/equalshashcode/Money.java
@@ -0,0 +1,36 @@
+package com.baeldung.equalshashcode;
+
+class Money {
+
+ int amount;
+ String currencyCode;
+
+ Money(int amount, String currencyCode) {
+ this.amount = amount;
+ this.currencyCode = currencyCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (!(o instanceof Money))
+ return false;
+ Money other = (Money)o;
+ boolean currencyCodeEquals = (this.currencyCode == null && other.currencyCode == null)
+ || (this.currencyCode != null && this.currencyCode.equals(other.currencyCode));
+ return this.amount == other.amount
+ && currencyCodeEquals;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + amount;
+ if (currencyCode != null) {
+ result = 31 * result + currencyCode.hashCode();
+ }
+ return result;
+ }
+
+}
diff --git a/core-java-lang/src/main/java/com/baeldung/equalshashcode/Team.java b/core-java-lang/src/main/java/com/baeldung/equalshashcode/Team.java
new file mode 100644
index 0000000000..c2dee1de6b
--- /dev/null
+++ b/core-java-lang/src/main/java/com/baeldung/equalshashcode/Team.java
@@ -0,0 +1,39 @@
+package com.baeldung.equalshashcode;
+
+class Team {
+
+ final String city;
+ final String department;
+
+ Team(String city, String department) {
+ this.city = city;
+ this.department = department;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (!(o instanceof Team))
+ return false;
+ Team otherTeam = (Team)o;
+ boolean cityEquals = (this.city == null && otherTeam.city == null)
+ || this.city != null && this.city.equals(otherTeam.city);
+ boolean departmentEquals = (this.department == null && otherTeam.department == null)
+ || this.department != null && this.department.equals(otherTeam.department);
+ return cityEquals && departmentEquals;
+ }
+
+ @Override
+ public final int hashCode() {
+ int result = 17;
+ if (city != null) {
+ result = 31 * result + city.hashCode();
+ }
+ if (department != null) {
+ result = 31 * result + department.hashCode();
+ }
+ return result;
+ }
+
+}
diff --git a/core-java-lang/src/main/java/com/baeldung/equalshashcode/Voucher.java b/core-java-lang/src/main/java/com/baeldung/equalshashcode/Voucher.java
new file mode 100644
index 0000000000..19f46e0358
--- /dev/null
+++ b/core-java-lang/src/main/java/com/baeldung/equalshashcode/Voucher.java
@@ -0,0 +1,38 @@
+package com.baeldung.equalshashcode;
+
+class Voucher {
+
+ private Money value;
+ private String store;
+
+ Voucher(int amount, String currencyCode, String store) {
+ this.value = new Money(amount, currencyCode);
+ this.store = store;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (!(o instanceof Voucher))
+ return false;
+ Voucher other = (Voucher)o;
+ boolean valueEquals = (this.value == null && other.value == null)
+ || (this.value != null && this.value.equals(other.value));
+ boolean storeEquals = (this.store == null && other.store == null)
+ || (this.store != null && this.store.equals(other.store));
+ return valueEquals && storeEquals;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ if (this.value != null) {
+ result = 31 * result + value.hashCode();
+ }
+ if (this.store != null) {
+ result = 31 * result + store.hashCode();
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/core-java-lang/src/main/java/com/baeldung/equalshashcode/WrongTeam.java b/core-java-lang/src/main/java/com/baeldung/equalshashcode/WrongTeam.java
new file mode 100644
index 0000000000..c4477aa790
--- /dev/null
+++ b/core-java-lang/src/main/java/com/baeldung/equalshashcode/WrongTeam.java
@@ -0,0 +1,30 @@
+package com.baeldung.equalshashcode;
+
+/* (non-Javadoc)
+* This class overrides equals, but it doesn't override hashCode.
+*
+* To see which problems this leads to:
+* TeamUnitTest.givenMapKeyWithoutHashCode_whenSearched_thenReturnsWrongValue
+*/
+class WrongTeam {
+
+ String city;
+ String department;
+
+ WrongTeam(String city, String department) {
+ this.city = city;
+ this.department = department;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (!(o instanceof WrongTeam))
+ return false;
+ WrongTeam otherTeam = (WrongTeam)o;
+ return this.city == otherTeam.city
+ && this.department == otherTeam.department;
+ }
+
+}
diff --git a/core-java-lang/src/main/java/com/baeldung/equalshashcode/WrongVoucher.java b/core-java-lang/src/main/java/com/baeldung/equalshashcode/WrongVoucher.java
new file mode 100644
index 0000000000..97935bf8de
--- /dev/null
+++ b/core-java-lang/src/main/java/com/baeldung/equalshashcode/WrongVoucher.java
@@ -0,0 +1,47 @@
+package com.baeldung.equalshashcode;
+
+/* (non-Javadoc)
+* This class extends the Money class that has overridden the equals method and once again overrides the equals method.
+*
+* To see which problems this leads to:
+* MoneyUnitTest.givenMoneyAndVoucherInstances_whenEquals_thenReturnValuesArentSymmetric
+*/
+class WrongVoucher extends Money {
+
+ private String store;
+
+ WrongVoucher(int amount, String currencyCode, String store) {
+ super(amount, currencyCode);
+
+ this.store = store;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (!(o instanceof WrongVoucher))
+ return false;
+ WrongVoucher other = (WrongVoucher)o;
+ boolean currencyCodeEquals = (this.currencyCode == null && other.currencyCode == null)
+ || (this.currencyCode != null && this.currencyCode.equals(other.currencyCode));
+ boolean storeEquals = (this.store == null && other.store == null)
+ || (this.store != null && this.store.equals(other.store));
+ return this.amount == other.amount
+ && currencyCodeEquals
+ && storeEquals;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + amount;
+ if (this.currencyCode != null) {
+ result = 31 * result + currencyCode.hashCode();
+ }
+ if (this.store != null) {
+ result = 31 * result + store.hashCode();
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/core-java-lang/src/test/java/com/baeldung/equalshashcode/MoneyUnitTest.java b/core-java-lang/src/test/java/com/baeldung/equalshashcode/MoneyUnitTest.java
new file mode 100644
index 0000000000..60584fdb53
--- /dev/null
+++ b/core-java-lang/src/test/java/com/baeldung/equalshashcode/MoneyUnitTest.java
@@ -0,0 +1,27 @@
+package com.baeldung.equalshashcode;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+public class MoneyUnitTest {
+
+ @Test
+ public void givenMoneyInstancesWithSameAmountAndCurrency_whenEquals_thenReturnsTrue() {
+ Money income = new Money(55, "USD");
+ Money expenses = new Money(55, "USD");
+
+ assertTrue(income.equals(expenses));
+ }
+
+ @Test
+ public void givenMoneyAndVoucherInstances_whenEquals_thenReturnValuesArentSymmetric() {
+ Money cash = new Money(42, "USD");
+ WrongVoucher voucher = new WrongVoucher(42, "USD", "Amazon");
+
+ assertFalse(voucher.equals(cash));
+ assertTrue(cash.equals(voucher));
+ }
+
+}
diff --git a/core-java-lang/src/test/java/com/baeldung/equalshashcode/TeamUnitTest.java b/core-java-lang/src/test/java/com/baeldung/equalshashcode/TeamUnitTest.java
new file mode 100644
index 0000000000..a2de408796
--- /dev/null
+++ b/core-java-lang/src/test/java/com/baeldung/equalshashcode/TeamUnitTest.java
@@ -0,0 +1,46 @@
+package com.baeldung.equalshashcode;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class TeamUnitTest {
+
+ @Test
+ public void givenMapKeyWithHashCode_whenSearched_thenReturnsCorrectValue() {
+ Map leaders = new HashMap<>();
+ leaders.put(new Team("New York", "development"), "Anne");
+ leaders.put(new Team("Boston", "development"), "Brian");
+ leaders.put(new Team("Boston", "marketing"), "Charlie");
+
+ Team myTeam = new Team("New York", "development");
+ String myTeamleader = leaders.get(myTeam);
+
+ assertEquals("Anne", myTeamleader);
+ }
+
+ @Test
+ public void givenMapKeyWithoutHashCode_whenSearched_thenReturnsWrongValue() {
+ Map leaders = new HashMap<>();
+ leaders.put(new WrongTeam("New York", "development"), "Anne");
+ leaders.put(new WrongTeam("Boston", "development"), "Brian");
+ leaders.put(new WrongTeam("Boston", "marketing"), "Charlie");
+
+ WrongTeam myTeam = new WrongTeam("New York", "development");
+ String myTeamleader = leaders.get(myTeam);
+
+ assertFalse("Anne".equals(myTeamleader));
+ }
+
+ @Test
+ public void equalsContract() {
+ EqualsVerifier.forClass(Team.class).verify();
+ }
+
+}
diff --git a/java-collections-conversions/src/test/java/org/baeldung/convertarraytostring/ArrayToStringUnitTest.java b/java-collections-conversions/src/test/java/org/baeldung/convertarraytostring/ArrayToStringUnitTest.java
new file mode 100644
index 0000000000..b563475997
--- /dev/null
+++ b/java-collections-conversions/src/test/java/org/baeldung/convertarraytostring/ArrayToStringUnitTest.java
@@ -0,0 +1,136 @@
+package org.baeldung.convertarraytostring;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class ArrayToStringUnitTest {
+
+ // convert with Java
+
+ @Test
+ public void givenAStringArray_whenConvertBeforeJava8_thenReturnString() {
+
+ String[] strArray = { "Convert", "Array", "With", "Java" };
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (int i = 0; i < strArray.length; i++) {
+ stringBuilder.append(strArray[i]);
+ }
+ String joinedString = stringBuilder.toString();
+
+ assertThat(joinedString, instanceOf(String.class));
+ assertEquals("ConvertArrayWithJava", joinedString);
+ }
+
+ @Test
+ public void givenAString_whenConvertBeforeJava8_thenReturnStringArray() {
+
+ String input = "lorem ipsum dolor sit amet";
+ String[] strArray = input.split(" ");
+
+ assertThat(strArray, instanceOf(String[].class));
+ assertEquals(5, strArray.length);
+
+ input = "loremipsum";
+ strArray = input.split("");
+ assertThat(strArray, instanceOf(String[].class));
+ assertEquals(10, strArray.length);
+ }
+
+ @Test
+ public void givenAnIntArray_whenConvertBeforeJava8_thenReturnString() {
+
+ int[] strArray = { 1, 2, 3, 4, 5 };
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (int i = 0; i < strArray.length; i++) {
+ stringBuilder.append(Integer.valueOf(strArray[i]));
+ }
+ String joinedString = stringBuilder.toString();
+
+ assertThat(joinedString, instanceOf(String.class));
+ assertEquals("12345", joinedString);
+ }
+
+ // convert with Java Stream API
+
+ @Test
+ public void givenAStringArray_whenConvertWithJavaStream_thenReturnString() {
+
+ String[] strArray = { "Convert", "With", "Java", "Streams" };
+ String joinedString = Arrays.stream(strArray)
+ .collect(Collectors.joining());
+ assertThat(joinedString, instanceOf(String.class));
+ assertEquals("ConvertWithJavaStreams", joinedString);
+
+ joinedString = Arrays.stream(strArray)
+ .collect(Collectors.joining(","));
+ assertThat(joinedString, instanceOf(String.class));
+ assertEquals("Convert,With,Java,Streams", joinedString);
+ }
+
+
+ // convert with Apache Commons
+
+ @Test
+ public void givenAStringArray_whenConvertWithApacheCommons_thenReturnString() {
+
+ String[] strArray = { "Convert", "With", "Apache", "Commons" };
+ String joinedString = StringUtils.join(strArray);
+
+ assertThat(joinedString, instanceOf(String.class));
+ assertEquals("ConvertWithApacheCommons", joinedString);
+ }
+
+ @Test
+ public void givenAString_whenConvertWithApacheCommons_thenReturnStringArray() {
+
+ String input = "lorem ipsum dolor sit amet";
+ String[] strArray = StringUtils.split(input, " ");
+
+ assertThat(strArray, instanceOf(String[].class));
+ assertEquals(5, strArray.length);
+ }
+
+
+ // convert with Guava
+
+ @Test
+ public void givenAStringArray_whenConvertWithGuava_thenReturnString() {
+
+ String[] strArray = { "Convert", "With", "Guava", null };
+ String joinedString = Joiner.on("")
+ .skipNulls()
+ .join(strArray);
+
+ assertThat(joinedString, instanceOf(String.class));
+ assertEquals("ConvertWithGuava", joinedString);
+ }
+
+
+ @Test
+ public void givenAString_whenConvertWithGuava_thenReturnStringArray() {
+
+ String input = "lorem ipsum dolor sit amet";
+
+ List resultList = Splitter.on(' ')
+ .trimResults()
+ .omitEmptyStrings()
+ .splitToList(input);
+ String[] strArray = resultList.toArray(new String[0]);
+
+ assertThat(strArray, instanceOf(String[].class));
+ assertEquals(5, strArray.length);
+ }
+}
diff --git a/java-strings/README.md b/java-strings/README.md
index 2847a0d0a8..222b28e8ad 100644
--- a/java-strings/README.md
+++ b/java-strings/README.md
@@ -37,3 +37,4 @@
- [Using indexOf to Find All Occurrences of a Word in a String](https://www.baeldung.com/java-indexOf-find-string-occurrences)
- [Java Base64 Encoding and Decoding](https://www.baeldung.com/java-base64-encode-and-decode)
- [Generate a Secure Random Password in Java](https://www.baeldung.com/java-generate-secure-password)
+- [Removing Repeated Characters from a String](https://www.baeldung.com/java-remove-repeated-char)
diff --git a/java-strings/src/test/java/com/baeldung/string/StringFromPrimitiveArrayUnitTest.java b/java-strings/src/test/java/com/baeldung/string/StringFromPrimitiveArrayUnitTest.java
new file mode 100644
index 0000000000..c93089e543
--- /dev/null
+++ b/java-strings/src/test/java/com/baeldung/string/StringFromPrimitiveArrayUnitTest.java
@@ -0,0 +1,129 @@
+package com.baeldung.string;
+
+import com.google.common.base.Joiner;
+import com.google.common.primitives.Chars;
+import com.google.common.primitives.Ints;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Test;
+
+import java.nio.CharBuffer;
+import java.util.Arrays;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class StringFromPrimitiveArrayUnitTest {
+
+ private int[] intArray = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ private char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f'};
+
+ private char separatorChar = '-';
+
+ private String separator = String.valueOf(separatorChar);
+
+ private String expectedIntString = "1-2-3-4-5-6-7-8-9";
+
+ private String expectedCharString = "a-b-c-d-e-f";
+
+ @Test
+ public void givenIntArray_whenJoinBySeparator_thenReturnsString_through_Java8CollectorsJoining() {
+ assertThat(Arrays.stream(intArray)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(separator)))
+ .isEqualTo(expectedIntString);
+ }
+
+ @Test
+ public void givenCharArray_whenJoinBySeparator_thenReturnsString_through_Java8CollectorsJoining() {
+ assertThat(CharBuffer.wrap(charArray).chars()
+ .mapToObj(intChar -> String.valueOf((char) intChar))
+ .collect(Collectors.joining(separator)))
+ .isEqualTo(expectedCharString);
+ }
+
+
+ @Test
+ public void giveIntArray_whenJoinBySeparator_thenReturnsString_through_Java8StringJoiner() {
+ StringJoiner intStringJoiner = new StringJoiner(separator);
+
+ Arrays.stream(intArray)
+ .mapToObj(String::valueOf)
+ .forEach(intStringJoiner::add);
+
+ assertThat(intStringJoiner.toString()).isEqualTo(expectedIntString);
+ }
+
+ @Test
+ public void givenCharArray_whenJoinBySeparator_thenReturnsString_through_Java8StringJoiner() {
+ StringJoiner charStringJoiner = new StringJoiner(separator);
+
+ CharBuffer.wrap(charArray).chars()
+ .mapToObj(intChar -> String.valueOf((char) intChar))
+ .forEach(charStringJoiner::add);
+
+ assertThat(charStringJoiner.toString()).isEqualTo(expectedCharString);
+ }
+
+ @Test
+ public void givenIntArray_whenJoinBySeparator_thenReturnsString_through_CommonsLang() {
+ assertThat(StringUtils.join(intArray, separatorChar)).isEqualTo(expectedIntString);
+ assertThat(StringUtils.join(ArrayUtils.toObject(intArray), separator)).isEqualTo(expectedIntString);
+ }
+
+ @Test
+ public void givenCharArray_whenJoinBySeparator_thenReturnsString_through_CommonsLang() {
+ assertThat(StringUtils.join(charArray, separatorChar)).isEqualTo(expectedCharString);
+ assertThat(StringUtils.join(ArrayUtils.toObject(charArray), separator)).isEqualTo(expectedCharString);
+ }
+
+ @Test
+ public void givenIntArray_whenJoinBySeparator_thenReturnsString_through_GuavaJoiner() {
+ assertThat(Joiner.on(separator).join(Ints.asList(intArray))).isEqualTo(expectedIntString);
+ }
+
+ @Test
+ public void givenCharArray_whenJoinBySeparator_thenReturnsString_through_GuavaJoiner() {
+ assertThat(Joiner.on(separator).join(Chars.asList(charArray))).isEqualTo(expectedCharString);
+ }
+
+ @Test
+ public void givenIntArray_whenJoinBySeparator_thenReturnsString_through_Java7StringBuilder() {
+ assertThat(joinIntArrayWithStringBuilder(intArray, separator)).isEqualTo(expectedIntString);
+ }
+
+ @Test
+ public void givenCharArray_whenJoinBySeparator_thenReturnsString_through_Java7StringBuilder() {
+ assertThat(joinCharArrayWithStringBuilder(charArray, separator)).isEqualTo(expectedCharString);
+ }
+
+ private String joinIntArrayWithStringBuilder(int[] array, String separator) {
+ if (array.length == 0) {
+ return "";
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < array.length - 1; i++) {
+ stringBuilder.append(array[i]);
+ stringBuilder.append(separator);
+ }
+ stringBuilder.append(array[array.length - 1]);
+ return stringBuilder.toString();
+ }
+
+ private String joinCharArrayWithStringBuilder(char[] array, String separator) {
+ if (array.length == 0) {
+ return "";
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < array.length - 1; i++) {
+ stringBuilder.append(array[i]);
+ stringBuilder.append(separator);
+ }
+ stringBuilder.append(array[array.length - 1]);
+ return stringBuilder.toString();
+ }
+
+}
diff --git a/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/findall/FindAll.java b/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/findall/FindAll.java
new file mode 100644
index 0000000000..cc0c234df0
--- /dev/null
+++ b/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/findall/FindAll.java
@@ -0,0 +1,35 @@
+package com.baeldung.hibernate.findall;
+
+import java.util.List;
+
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Root;
+
+import org.hibernate.Session;
+
+import com.baeldung.hibernate.pojo.Student;
+
+public class FindAll {
+
+ private Session session;
+
+ public FindAll(Session session) {
+ super();
+ this.session = session;
+ }
+
+ public List findAllWithJpql() {
+ return session.createQuery("SELECT a FROM Student a", Student.class).getResultList();
+ }
+
+ public List findAllWithCriteriaQuery() {
+ CriteriaBuilder cb = session.getCriteriaBuilder();
+ CriteriaQuery cq = cb.createQuery(Student.class);
+ Root rootEntry = cq.from(Student.class);
+ CriteriaQuery all = cq.select(rootEntry);
+ TypedQuery allQuery = session.createQuery(all);
+ return allQuery.getResultList();
+ }
+}
diff --git a/persistence-modules/hibernate5/src/test/java/com/baeldung/hibernate/findall/FindAllUnitTest.java b/persistence-modules/hibernate5/src/test/java/com/baeldung/hibernate/findall/FindAllUnitTest.java
new file mode 100644
index 0000000000..8a1b9e9791
--- /dev/null
+++ b/persistence-modules/hibernate5/src/test/java/com/baeldung/hibernate/findall/FindAllUnitTest.java
@@ -0,0 +1,63 @@
+package com.baeldung.hibernate.findall;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baeldung.hibernate.HibernateUtil;
+import com.baeldung.hibernate.pojo.Student;
+
+public class FindAllUnitTest {
+
+ private Session session;
+ private Transaction transaction;
+
+ private FindAll findAll;
+
+ @Before
+ public void setUp() throws IOException {
+
+ session = HibernateUtil.getSessionFactory().openSession();
+ transaction = session.beginTransaction();
+ findAll = new FindAll(session);
+
+ session.createNativeQuery("delete from Student").executeUpdate();
+
+ Student student1 = new Student();
+ session.persist(student1);
+
+ Student student2 = new Student();
+ session.persist(student2);
+
+ Student student3 = new Student();
+ session.persist(student3);
+
+ transaction.commit();
+ transaction = session.beginTransaction();
+ }
+
+ @After
+ public void tearDown() {
+ transaction.rollback();
+ session.close();
+ }
+
+ @Test
+ public void givenCriteriaQuery_WhenFindAll_ThenGetAllPersons() {
+ List list = findAll.findAllWithCriteriaQuery();
+ assertEquals(3, list.size());
+ }
+
+ @Test
+ public void givenJpql_WhenFindAll_ThenGetAllPersons() {
+ List list = findAll.findAllWithJpql();
+ assertEquals(3, list.size());
+ }
+}
diff --git a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/PassengerRepository.java b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/PassengerRepository.java
deleted file mode 100644
index 6301f6e307..0000000000
--- a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/PassengerRepository.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.baeldung.limit;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-
-interface PassengerRepository extends JpaRepository, CustomPassengerRepository {
-
- Passenger findFirstByOrderBySeatNumberAsc();
-}
diff --git a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/CustomPassengerRepository.java b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/CustomPassengerRepository.java
similarity index 80%
rename from persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/CustomPassengerRepository.java
rename to persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/CustomPassengerRepository.java
index e9b5ba272f..7ae44bfbda 100644
--- a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/CustomPassengerRepository.java
+++ b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/CustomPassengerRepository.java
@@ -1,4 +1,4 @@
-package com.baeldung.limit;
+package com.baeldung.passenger;
import java.util.List;
diff --git a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/Passenger.java b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/Passenger.java
similarity index 80%
rename from persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/Passenger.java
rename to persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/Passenger.java
index e7eb3af10b..24ae47e597 100644
--- a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/Passenger.java
+++ b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/Passenger.java
@@ -1,4 +1,4 @@
-package com.baeldung.limit;
+package com.baeldung.passenger;
import javax.persistence.Basic;
import javax.persistence.Column;
@@ -17,7 +17,7 @@ class Passenger {
@Basic(optional = false)
@Column(nullable = false)
- private String fistName;
+ private String firstName;
@Basic(optional = false)
@Column(nullable = false)
@@ -27,8 +27,8 @@ class Passenger {
@Column(nullable = false)
private int seatNumber;
- private Passenger(String fistName, String lastName, int seatNumber) {
- this.fistName = fistName;
+ private Passenger(String firstName, String lastName, int seatNumber) {
+ this.firstName = firstName;
this.lastName = lastName;
this.seatNumber = seatNumber;
}
@@ -44,20 +44,20 @@ class Passenger {
if (object == null || getClass() != object.getClass())
return false;
Passenger passenger = (Passenger) object;
- return getSeatNumber() == passenger.getSeatNumber() && Objects.equals(getFistName(), passenger.getFistName())
+ return getSeatNumber() == passenger.getSeatNumber() && Objects.equals(getFirstName(), passenger.getFirstName())
&& Objects.equals(getLastName(), passenger.getLastName());
}
@Override
public int hashCode() {
- return Objects.hash(getFistName(), getLastName(), getSeatNumber());
+ return Objects.hash(getFirstName(), getLastName(), getSeatNumber());
}
@Override
public String toString() {
final StringBuilder toStringBuilder = new StringBuilder(getClass().getSimpleName());
toStringBuilder.append("{ id=").append(id);
- toStringBuilder.append(", fistName='").append(fistName).append('\'');
+ toStringBuilder.append(", firstName='").append(firstName).append('\'');
toStringBuilder.append(", lastName='").append(lastName).append('\'');
toStringBuilder.append(", seatNumber=").append(seatNumber);
toStringBuilder.append('}');
@@ -68,8 +68,8 @@ class Passenger {
return id;
}
- String getFistName() {
- return fistName;
+ String getFirstName() {
+ return firstName;
}
String getLastName() {
diff --git a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/PassengerRepository.java b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/PassengerRepository.java
new file mode 100644
index 0000000000..6ae6afb403
--- /dev/null
+++ b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/PassengerRepository.java
@@ -0,0 +1,17 @@
+package com.baeldung.passenger;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+interface PassengerRepository extends JpaRepository, CustomPassengerRepository {
+
+ Passenger findFirstByOrderBySeatNumberAsc();
+
+ List findByOrderBySeatNumberAsc();
+
+ List findByLastNameOrderBySeatNumberAsc(String lastName);
+
+ List findByLastName(String lastName, Sort sort);
+}
diff --git a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/PassengerRepositoryImpl.java b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/PassengerRepositoryImpl.java
similarity index 94%
rename from persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/PassengerRepositoryImpl.java
rename to persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/PassengerRepositoryImpl.java
index 2d13eb7cad..bd6e535e3e 100644
--- a/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/limit/PassengerRepositoryImpl.java
+++ b/persistence-modules/spring-data-jpa/src/main/java/com/baeldung/passenger/PassengerRepositoryImpl.java
@@ -1,4 +1,4 @@
-package com.baeldung.limit;
+package com.baeldung.passenger;
import org.springframework.stereotype.Repository;
diff --git a/persistence-modules/spring-data-jpa/src/test/java/com/baeldung/limit/LimitIntegrationTest.java b/persistence-modules/spring-data-jpa/src/test/java/com/baeldung/passenger/PassengerRepositoryIntegrationTest.java
similarity index 63%
rename from persistence-modules/spring-data-jpa/src/test/java/com/baeldung/limit/LimitIntegrationTest.java
rename to persistence-modules/spring-data-jpa/src/test/java/com/baeldung/passenger/PassengerRepositoryIntegrationTest.java
index 27e0ebdd9f..c57e771345 100644
--- a/persistence-modules/spring-data-jpa/src/test/java/com/baeldung/limit/LimitIntegrationTest.java
+++ b/persistence-modules/spring-data-jpa/src/test/java/com/baeldung/passenger/PassengerRepositoryIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.baeldung.limit;
+package com.baeldung.passenger;
import org.junit.Before;
import org.junit.Test;
@@ -14,11 +14,13 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
@DataJpaTest
@RunWith(SpringRunner.class)
-public class LimitIntegrationTest {
+public class PassengerRepositoryIntegrationTest {
@PersistenceContext
private EntityManager entityManager;
@@ -66,4 +68,31 @@ public class LimitIntegrationTest {
Passenger actual = page.getContent().get(0);
assertEquals(expected, actual);
}
+
+ @Test
+ public void givenPassengers_whenOrderedBySeatNumberAsc_thenCorrectOrder() {
+ Passenger fred = Passenger.from("Fred", "Bloggs", 22);
+ Passenger ricki = Passenger.from("Ricki", "Bobbie", 36);
+ Passenger jill = Passenger.from("Jill", "Smith", 50);
+ Passenger siya = Passenger.from("Siya", "Kolisi", 85);
+ Passenger eve = Passenger.from("Eve", "Jackson", 95);
+
+ List passengers = repository.findByOrderBySeatNumberAsc();
+
+ assertThat(passengers, contains(fred, ricki, jill, siya, eve));
+ }
+
+ @Test
+ public void givenPassengers_whenFindAllWithSortBySeatNumberAsc_thenCorrectOrder() {
+ Passenger fred = Passenger.from("Fred", "Bloggs", 22);
+ Passenger ricki = Passenger.from("Ricki", "Bobbie", 36);
+ Passenger jill = Passenger.from("Jill", "Smith", 50);
+ Passenger siya = Passenger.from("Siya", "Kolisi", 85);
+ Passenger eve = Passenger.from("Eve", "Jackson", 95);
+
+ List passengers = repository.findAll(Sort.by(Sort.Direction.ASC, "seatNumber"));
+
+ assertThat(passengers, contains(fred, ricki, jill, siya, eve));
+ }
+
}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/ConsumerSSEApplication.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/ConsumerSSEApplication.java
new file mode 100644
index 0000000000..55db3d7392
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/ConsumerSSEApplication.java
@@ -0,0 +1,33 @@
+package com.baeldung.debugging.consumer;
+
+import java.util.Collections;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+import reactor.core.publisher.Hooks;
+
+@SpringBootApplication(exclude = MongoReactiveAutoConfiguration.class)
+@EnableScheduling
+public class ConsumerSSEApplication {
+
+ public static void main(String[] args) {
+ Hooks.onOperatorDebug();
+ SpringApplication app = new SpringApplication(ConsumerSSEApplication.class);
+ app.setDefaultProperties(Collections.singletonMap("server.port", "8082"));
+ app.run(args);
+ }
+
+ @Bean
+ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ http.authorizeExchange()
+ .anyExchange()
+ .permitAll();
+ return http.build();
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/chronjobs/ChronJobs.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/chronjobs/ChronJobs.java
new file mode 100644
index 0000000000..09cbc34a6f
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/chronjobs/ChronJobs.java
@@ -0,0 +1,122 @@
+package com.baeldung.debugging.consumer.chronjobs;
+
+import java.time.Duration;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import com.baeldung.debugging.consumer.model.Foo;
+import com.baeldung.debugging.consumer.model.FooDto;
+import com.baeldung.debugging.consumer.service.FooService;
+
+import reactor.core.publisher.Flux;
+
+@Component
+public class ChronJobs {
+
+ private static Logger logger = LoggerFactory.getLogger(ChronJobs.class);
+ private WebClient client = WebClient.create("http://localhost:8081");
+
+ @Autowired
+ private FooService service;
+
+ @Scheduled(fixedRate = 10000)
+ public void consumeInfiniteFlux() {
+ Flux fluxFoo = client.get()
+ .uri("/functional-reactive/periodic-foo")
+ .accept(MediaType.TEXT_EVENT_STREAM)
+ .retrieve()
+ .bodyToFlux(FooDto.class)
+ .delayElements(Duration.ofMillis(100))
+ .map(dto -> {
+ logger.debug("process 1 with dto id {} name{}", dto.getId(), dto.getName());
+ return new Foo(dto);
+ });
+ Integer random = ThreadLocalRandom.current()
+ .nextInt(0, 3);
+ switch (random) {
+ case 0:
+ logger.info("process 1 with approach 1");
+ service.processFoo(fluxFoo);
+ break;
+ case 1:
+ logger.info("process 1 with approach 1 EH");
+ service.processUsingApproachOneWithErrorHandling(fluxFoo);
+ break;
+ default:
+ logger.info("process 1 with approach 2");
+ service.processFooInAnotherScenario(fluxFoo);
+ break;
+
+ }
+ }
+
+ @Scheduled(fixedRate = 20000)
+ public void consumeFiniteFlux2() {
+ Flux fluxFoo = client.get()
+ .uri("/functional-reactive/periodic-foo-2")
+ .accept(MediaType.TEXT_EVENT_STREAM)
+ .retrieve()
+ .bodyToFlux(FooDto.class)
+ .delayElements(Duration.ofMillis(100))
+ .map(dto -> {
+ logger.debug("process 2 with dto id {} name{}", dto.getId(), dto.getName());
+ return new Foo(dto);
+ });
+ Integer random = ThreadLocalRandom.current()
+ .nextInt(0, 3);
+ switch (random) {
+ case 0:
+ logger.info("process 2 with approach 1");
+ service.processFoo(fluxFoo);
+ break;
+ case 1:
+ logger.info("process 2 with approach 1 EH");
+ service.processUsingApproachOneWithErrorHandling(fluxFoo);
+ break;
+ default:
+ logger.info("process 2 with approach 2");
+ service.processFooInAnotherScenario(fluxFoo);
+ break;
+
+ }
+ }
+
+ @Scheduled(fixedRate = 20000)
+ public void consumeFiniteFlux3() {
+ Flux fluxFoo = client.get()
+ .uri("/functional-reactive/periodic-foo-2")
+ .accept(MediaType.TEXT_EVENT_STREAM)
+ .retrieve()
+ .bodyToFlux(FooDto.class)
+ .delayElements(Duration.ofMillis(100))
+ .map(dto -> {
+ logger.debug("process 3 with dto id {} name{}", dto.getId(), dto.getName());
+ return new Foo(dto);
+ });
+ logger.info("process 3 with approach 3");
+ service.processUsingApproachThree(fluxFoo);
+ }
+
+ @Scheduled(fixedRate = 20000)
+ public void consumeFiniteFluxWithCheckpoint4() {
+ Flux fluxFoo = client.get()
+ .uri("/functional-reactive/periodic-foo-2")
+ .accept(MediaType.TEXT_EVENT_STREAM)
+ .retrieve()
+ .bodyToFlux(FooDto.class)
+ .delayElements(Duration.ofMillis(100))
+ .map(dto -> {
+ logger.debug("process 4 with dto id {} name{}", dto.getId(), dto.getName());
+ return new Foo(dto);
+ });
+ logger.info("process 4 with approach 4");
+ service.processUsingApproachFourWithCheckpoint(fluxFoo);
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/controllers/ReactiveConfigsToggleRestController.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/controllers/ReactiveConfigsToggleRestController.java
new file mode 100644
index 0000000000..3dcdc6c7c0
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/controllers/ReactiveConfigsToggleRestController.java
@@ -0,0 +1,23 @@
+package com.baeldung.debugging.consumer.controllers;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import reactor.core.publisher.Hooks;
+
+@RestController
+public class ReactiveConfigsToggleRestController {
+
+ @GetMapping("/debug-hook-on")
+ public String setReactiveDebugOn() {
+ Hooks.onOperatorDebug();
+ return "DEBUG HOOK ON";
+ }
+
+ @GetMapping("/debug-hook-off")
+ public String setReactiveDebugOff() {
+ Hooks.resetOnOperatorDebug();
+ return "DEBUG HOOK OFF";
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/model/Foo.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/model/Foo.java
new file mode 100644
index 0000000000..e101457b84
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/model/Foo.java
@@ -0,0 +1,26 @@
+package com.baeldung.debugging.consumer.model;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.springframework.data.annotation.Id;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class Foo {
+
+ @Id
+ private Integer id;
+ private String formattedName;
+ private Integer quantity;
+
+ public Foo(FooDto dto) {
+ this.id = (ThreadLocalRandom.current()
+ .nextInt(0, 100) == 0) ? null : dto.getId();
+ this.formattedName = dto.getName();
+ this.quantity = ThreadLocalRandom.current()
+ .nextInt(0, 10);
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/model/FooDto.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/model/FooDto.java
new file mode 100644
index 0000000000..50508fd216
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/model/FooDto.java
@@ -0,0 +1,12 @@
+package com.baeldung.debugging.consumer.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class FooDto {
+
+ private Integer id;
+ private String name;
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooNameHelper.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooNameHelper.java
new file mode 100644
index 0000000000..772b360437
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooNameHelper.java
@@ -0,0 +1,45 @@
+package com.baeldung.debugging.consumer.service;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import com.baeldung.debugging.consumer.model.Foo;
+
+import reactor.core.publisher.Flux;
+
+public class FooNameHelper {
+
+ public static Flux concatAndSubstringFooName(Flux flux) {
+ flux = concatFooName(flux);
+ flux = substringFooName(flux);
+ return flux;
+ }
+
+ public static Flux concatFooName(Flux flux) {
+ flux = flux.map(foo -> {
+ String processedName = null;
+ Integer random = ThreadLocalRandom.current()
+ .nextInt(0, 80);
+ processedName = (random != 0) ? foo.getFormattedName() : foo.getFormattedName() + "-bael";
+ foo.setFormattedName(processedName);
+ return foo;
+ });
+ return flux;
+ }
+
+ public static Flux substringFooName(Flux flux) {
+ return flux.map(foo -> {
+ String processedName;
+ Integer random = ThreadLocalRandom.current()
+ .nextInt(0, 100);
+
+ processedName = (random == 0) ? foo.getFormattedName()
+ .substring(10, 15)
+ : foo.getFormattedName()
+ .substring(0, 5);
+
+ foo.setFormattedName(processedName);
+ return foo;
+ });
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooQuantityHelper.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooQuantityHelper.java
new file mode 100644
index 0000000000..615239313d
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooQuantityHelper.java
@@ -0,0 +1,31 @@
+package com.baeldung.debugging.consumer.service;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import com.baeldung.debugging.consumer.model.Foo;
+
+import reactor.core.publisher.Flux;
+
+public class FooQuantityHelper {
+
+ public static Flux processFooReducingQuantity(Flux flux) {
+ flux = flux.map(foo -> {
+ Integer result;
+ Integer random = ThreadLocalRandom.current()
+ .nextInt(0, 90);
+ result = (random == 0) ? result = 0 : foo.getQuantity() + 2;
+ foo.setQuantity(result);
+ return foo;
+ });
+ return divideFooQuantity(flux);
+ }
+
+ public static Flux divideFooQuantity(Flux flux) {
+ return flux.map(foo -> {
+ Integer result = Math.round(5 / foo.getQuantity());
+ foo.setQuantity(result);
+ return foo;
+ });
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooReporter.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooReporter.java
new file mode 100644
index 0000000000..f53cd238e0
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooReporter.java
@@ -0,0 +1,26 @@
+package com.baeldung.debugging.consumer.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.baeldung.debugging.consumer.model.Foo;
+
+import reactor.core.publisher.Flux;
+
+public class FooReporter {
+
+ private static Logger logger = LoggerFactory.getLogger(FooReporter.class);
+
+ public static Flux reportResult(Flux input, String approach) {
+ return input.map(foo -> {
+ if (foo.getId() == null)
+ throw new IllegalArgumentException("Null id is not valid!");
+ logger.info("Reporting for approach {}: Foo with id '{}' name '{}' and quantity '{}'", approach, foo.getId(), foo.getFormattedName(), foo.getQuantity());
+ return foo;
+ });
+ }
+
+ public static Flux reportResult(Flux input) {
+ return reportResult(input, "default");
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooService.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooService.java
new file mode 100644
index 0000000000..937e445ef5
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/consumer/service/FooService.java
@@ -0,0 +1,91 @@
+package com.baeldung.debugging.consumer.service;
+
+import static com.baeldung.debugging.consumer.service.FooNameHelper.concatAndSubstringFooName;
+import static com.baeldung.debugging.consumer.service.FooNameHelper.substringFooName;
+import static com.baeldung.debugging.consumer.service.FooQuantityHelper.divideFooQuantity;
+import static com.baeldung.debugging.consumer.service.FooQuantityHelper.processFooReducingQuantity;
+import static com.baeldung.debugging.consumer.service.FooReporter.reportResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.baeldung.debugging.consumer.model.Foo;
+
+import reactor.core.publisher.Flux;
+
+@Component
+public class FooService {
+
+ private static Logger logger = LoggerFactory.getLogger(FooService.class);
+
+ public void processFoo(Flux flux) {
+ flux = FooNameHelper.concatFooName(flux);
+ flux = FooNameHelper.substringFooName(flux);
+ flux = flux.log();
+ flux = FooReporter.reportResult(flux);
+ flux = flux.doOnError(error -> {
+ logger.error("The following error happened on processFoo method!", error);
+ });
+ flux.subscribe();
+ }
+
+ public void processFooInAnotherScenario(Flux flux) {
+ flux = FooNameHelper.substringFooName(flux);
+ flux = FooQuantityHelper.divideFooQuantity(flux);
+ flux.subscribe();
+ }
+
+ public void processUsingApproachOneWithErrorHandling(Flux flux) {
+ logger.info("starting approach one w error handling!");
+ flux = concatAndSubstringFooName(flux);
+ flux = concatAndSubstringFooName(flux);
+ flux = substringFooName(flux);
+ flux = processFooReducingQuantity(flux);
+ flux = processFooReducingQuantity(flux);
+ flux = processFooReducingQuantity(flux);
+ flux = reportResult(flux, "ONE w/ EH");
+ flux = flux.doOnError(error -> {
+ logger.error("Approach 1 with Error Handling failed!", error);
+ });
+ flux.subscribe();
+ }
+
+ public void processUsingApproachThree(Flux flux) {
+ logger.info("starting approach three!");
+ flux = concatAndSubstringFooName(flux);
+ flux = reportResult(flux, "THREE");
+ flux = flux.doOnError(error -> {
+ logger.error("Approach 3 failed!", error);
+ });
+ flux.subscribe();
+ }
+
+ public void processUsingApproachFourWithCheckpoint(Flux flux) {
+ logger.info("starting approach four!");
+ flux = concatAndSubstringFooName(flux);
+ flux = flux.checkpoint("CHECKPOINT 1");
+ flux = concatAndSubstringFooName(flux);
+ flux = divideFooQuantity(flux);
+ flux = flux.checkpoint("CHECKPOINT 2", true);
+ flux = reportResult(flux, "FOUR");
+ flux = concatAndSubstringFooName(flux).doOnError(error -> {
+ logger.error("Approach 4 failed!", error);
+ });
+ flux.subscribe();
+ }
+
+ public void processUsingApproachFourWithInitialCheckpoint(Flux flux) {
+ logger.info("starting approach four!");
+ flux = concatAndSubstringFooName(flux);
+ flux = flux.checkpoint("CHECKPOINT 1", true);
+ flux = concatAndSubstringFooName(flux);
+ flux = divideFooQuantity(flux);
+ flux = reportResult(flux, "FOUR");
+ flux = flux.doOnError(error -> {
+ logger.error("Approach 4-2 failed!", error);
+ });
+ flux.subscribe();
+ }
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/server/ServerSSEApplication.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/ServerSSEApplication.java
new file mode 100644
index 0000000000..6b24ee39f0
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/ServerSSEApplication.java
@@ -0,0 +1,29 @@
+package com.baeldung.debugging.server;
+
+import java.util.Collections;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.web.reactive.config.EnableWebFlux;
+
+@EnableWebFlux
+@SpringBootApplication
+public class ServerSSEApplication {
+
+ public static void main(String[] args) {
+ SpringApplication app = new SpringApplication(ServerSSEApplication.class);
+ app.setDefaultProperties(Collections.singletonMap("server.port", "8081"));
+ app.run(args);
+ }
+
+ @Bean
+ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ http.authorizeExchange()
+ .anyExchange()
+ .permitAll();
+ return http.build();
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/server/handlers/ServerHandler.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/handlers/ServerHandler.java
new file mode 100644
index 0000000000..759cd9b01d
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/handlers/ServerHandler.java
@@ -0,0 +1,47 @@
+package com.baeldung.debugging.server.handlers;
+
+import java.time.Duration;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+import com.baeldung.debugging.server.model.Foo;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Component
+public class ServerHandler {
+
+ private static Logger logger = LoggerFactory.getLogger(ServerHandler.class);
+
+ public Mono useHandler(final ServerRequest request) {
+ // there are chances that something goes wrong here...
+ return ServerResponse.ok()
+ .contentType(MediaType.TEXT_EVENT_STREAM)
+ .body(Flux.interval(Duration.ofSeconds(1))
+ .map(sequence -> {
+ logger.info("retrieving Foo. Sequence: {}", sequence);
+ if (ThreadLocalRandom.current()
+ .nextInt(0, 50) == 1) {
+ throw new RuntimeException("There was an error retrieving the Foo!");
+ }
+ return new Foo(sequence, "name" + sequence);
+
+ }), Foo.class);
+ }
+
+ public Mono useHandlerFinite(final ServerRequest request) {
+ return ServerResponse.ok()
+ .contentType(MediaType.TEXT_EVENT_STREAM)
+ .body(Flux.range(0, 50)
+ .map(sequence -> {
+ return new Foo(new Long(sequence), "theFooNameNumber" + sequence);
+ }), Foo.class);
+ }
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/server/model/Foo.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/model/Foo.java
new file mode 100644
index 0000000000..a60e468e7f
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/model/Foo.java
@@ -0,0 +1,16 @@
+package com.baeldung.debugging.server.model;
+
+import org.springframework.data.annotation.Id;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class Foo {
+
+ @Id
+ private Long id;
+ private String name;
+
+}
diff --git a/spring-5-reactive/src/main/java/com/baeldung/debugging/server/routers/ServerRouter.java b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/routers/ServerRouter.java
new file mode 100644
index 0000000000..6378b2213d
--- /dev/null
+++ b/spring-5-reactive/src/main/java/com/baeldung/debugging/server/routers/ServerRouter.java
@@ -0,0 +1,22 @@
+package com.baeldung.debugging.server.routers;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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 com.baeldung.debugging.server.handlers.ServerHandler;
+
+@Configuration
+public class ServerRouter {
+
+ @Bean
+ public RouterFunction responseRoute(@Autowired ServerHandler handler) {
+ return RouterFunctions.route(RequestPredicates.GET("/functional-reactive/periodic-foo"), handler::useHandler)
+ .andRoute(RequestPredicates.GET("/functional-reactive/periodic-foo-2"), handler::useHandlerFinite);
+ }
+
+}
diff --git a/spring-5-reactive/src/main/resources/application.properties b/spring-5-reactive/src/main/resources/application.properties
index 92f3116f84..4b49e8e8a2 100644
--- a/spring-5-reactive/src/main/resources/application.properties
+++ b/spring-5-reactive/src/main/resources/application.properties
@@ -1,2 +1 @@
-logging.level.root=INFO
-
+logging.level.root=INFO
\ No newline at end of file
diff --git a/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/ConsumerFooServiceIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/ConsumerFooServiceIntegrationTest.java
new file mode 100644
index 0000000000..b7ed031ec7
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/ConsumerFooServiceIntegrationTest.java
@@ -0,0 +1,65 @@
+package com.baeldung.debugging.consumer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.baeldung.debugging.consumer.model.Foo;
+import com.baeldung.debugging.consumer.service.FooService;
+import com.baeldung.debugging.consumer.utils.ListAppender;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Hooks;
+
+public class ConsumerFooServiceIntegrationTest {
+
+ FooService service = new FooService();
+
+ @BeforeEach
+ public void clearLogList() {
+ Hooks.onOperatorDebug();
+ ListAppender.clearEventList();
+ }
+
+ @Test
+ public void givenFooWithNullId_whenProcessFoo_thenLogsWithDebugTrace() {
+ Foo one = new Foo(1, "nameverylong", 8);
+ Foo two = new Foo(null, "nameverylong", 4);
+ Flux flux = Flux.just(one, two);
+
+ service.processFoo(flux);
+
+ Collection allLoggedEntries = ListAppender.getEvents()
+ .stream()
+ .map(ILoggingEvent::getFormattedMessage)
+ .collect(Collectors.toList());
+
+ Collection allSuppressedEntries = ListAppender.getEvents()
+ .stream()
+ .map(ILoggingEvent::getThrowableProxy)
+ .flatMap(t -> {
+ return Optional.ofNullable(t)
+ .map(IThrowableProxy::getSuppressed)
+ .map(Arrays::stream)
+ .orElse(Stream.empty());
+ })
+ .map(IThrowableProxy::getMessage)
+ .collect(Collectors.toList());
+ assertThat(allLoggedEntries).anyMatch(entry -> entry.contains("The following error happened on processFoo method!"))
+ .anyMatch(entry -> entry.contains("| onSubscribe"))
+ .anyMatch(entry -> entry.contains("| cancel()"));
+
+ assertThat(allSuppressedEntries).anyMatch(entry -> entry.contains("Assembly trace from producer"))
+ .anyMatch(entry -> entry.contains("Error has been observed by the following operator(s)"));
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/ConsumerFooServiceLiveTest.java b/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/ConsumerFooServiceLiveTest.java
new file mode 100644
index 0000000000..af9bdfbc9b
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/ConsumerFooServiceLiveTest.java
@@ -0,0 +1,49 @@
+package com.baeldung.debugging.consumer;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
+
+import com.baeldung.debugging.consumer.service.FooService;
+
+public class ConsumerFooServiceLiveTest {
+
+ FooService service = new FooService();
+
+ private static final String BASE_URL = "http://localhost:8082";
+ private static final String DEBUG_HOOK_ON = BASE_URL + "/debug-hook-on";
+ private static final String DEBUG_HOOK_OFF = BASE_URL + "/debug-hook-off";
+
+ private static WebTestClient client;
+
+ @BeforeAll
+ public static void setup() {
+ client = WebTestClient.bindToServer()
+ .baseUrl(BASE_URL)
+ .build();
+ }
+
+ @Test
+ public void whenRequestingDebugHookOn_thenObtainExpectedMessage() {
+ ResponseSpec response = client.get()
+ .uri(DEBUG_HOOK_ON)
+ .exchange();
+ response.expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("DEBUG HOOK ON");
+ }
+
+ @Test
+ public void whenRequestingDebugHookOff_thenObtainExpectedMessage() {
+ ResponseSpec response = client.get()
+ .uri(DEBUG_HOOK_OFF)
+ .exchange();
+ response.expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .isEqualTo("DEBUG HOOK OFF");
+ }
+
+}
diff --git a/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/utils/ListAppender.java b/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/utils/ListAppender.java
new file mode 100644
index 0000000000..c8c1c110bb
--- /dev/null
+++ b/spring-5-reactive/src/test/java/com/baeldung/debugging/consumer/utils/ListAppender.java
@@ -0,0 +1,25 @@
+package com.baeldung.debugging.consumer.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+
+public class ListAppender extends AppenderBase {
+
+ static private List events = new ArrayList<>();
+
+ @Override
+ protected void append(ILoggingEvent eventObject) {
+ events.add(eventObject);
+ }
+
+ public static List getEvents() {
+ return events;
+ }
+
+ public static void clearEventList() {
+ events.clear();
+ }
+}
diff --git a/spring-5-reactive/src/test/resources/logback-test.xml b/spring-5-reactive/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000..514029e402
--- /dev/null
+++ b/spring-5-reactive/src/test/resources/logback-test.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-boot-autoconfiguration/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfiguration/src/main/resources/META-INF/spring.factories
index 5f55544eff..11c775fc6c 100644
--- a/spring-boot-autoconfiguration/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-autoconfiguration/src/main/resources/META-INF/spring.factories
@@ -1,3 +1 @@
-org.springframework.boot.diagnostics.FailureAnalyzer=com.baeldung.failureanalyzer.MyBeanNotOfRequiredTypeFailureAnalyzer
-
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baeldung.autoconfiguration.MySQLAutoconfiguration
\ No newline at end of file
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baeldung.autoconfiguration.MySQLAutoconfiguration
diff --git a/spring-boot/src/main/java/com/baeldung/mongodb/Application.java b/spring-boot/src/main/java/com/baeldung/mongodb/Application.java
index 092ce3352b..c0a9ad59a7 100644
--- a/spring-boot/src/main/java/com/baeldung/mongodb/Application.java
+++ b/spring-boot/src/main/java/com/baeldung/mongodb/Application.java
@@ -1,11 +1,19 @@
package com.baeldung.mongodb;
+import com.baeldung.mongodb.daos.UserRepository;
+import com.baeldung.mongodb.models.User;
+import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+import java.util.List;
@SpringBootApplication
public class Application {
+
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
+
}
diff --git a/spring-boot/src/main/java/com/baeldung/mongodb/daos/UserRepository.java b/spring-boot/src/main/java/com/baeldung/mongodb/daos/UserRepository.java
new file mode 100755
index 0000000000..7f58fdd1c8
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/mongodb/daos/UserRepository.java
@@ -0,0 +1,10 @@
+package com.baeldung.mongodb.daos;
+
+
+import com.baeldung.mongodb.models.User;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+
+public interface UserRepository extends MongoRepository {
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/mongodb/events/UserModelListener.java b/spring-boot/src/main/java/com/baeldung/mongodb/events/UserModelListener.java
new file mode 100644
index 0000000000..24b53f3d2d
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/mongodb/events/UserModelListener.java
@@ -0,0 +1,28 @@
+package com.baeldung.mongodb.events;
+
+
+import com.baeldung.mongodb.models.User;
+import com.baeldung.mongodb.services.SequenceGeneratorService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
+import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
+import org.springframework.stereotype.Component;
+
+
+@Component
+public class UserModelListener extends AbstractMongoEventListener {
+
+ private SequenceGeneratorService sequenceGenerator;
+
+ @Autowired
+ public UserModelListener(SequenceGeneratorService sequenceGenerator) {
+ this.sequenceGenerator = sequenceGenerator;
+ }
+
+ @Override
+ public void onBeforeConvert(BeforeConvertEvent event) {
+ event.getSource().setId(sequenceGenerator.generateSequence(User.SEQUENCE_NAME));
+ }
+
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/mongodb/models/DatabaseSequence.java b/spring-boot/src/main/java/com/baeldung/mongodb/models/DatabaseSequence.java
new file mode 100755
index 0000000000..c2c04f7ee6
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/mongodb/models/DatabaseSequence.java
@@ -0,0 +1,32 @@
+package com.baeldung.mongodb.models;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+
+@Document(collection = "database_sequences")
+public class DatabaseSequence {
+
+ @Id
+ private String id;
+
+ private long seq;
+
+ public DatabaseSequence() {}
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public long getSeq() {
+ return seq;
+ }
+
+ public void setSeq(long seq) {
+ this.seq = seq;
+ }
+}
diff --git a/spring-boot/src/main/java/com/baeldung/mongodb/models/User.java b/spring-boot/src/main/java/com/baeldung/mongodb/models/User.java
new file mode 100755
index 0000000000..1f08074313
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/mongodb/models/User.java
@@ -0,0 +1,73 @@
+package com.baeldung.mongodb.models;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Transient;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+
+@Document(collection = "users")
+public class User {
+
+ @Transient
+ public static final String SEQUENCE_NAME = "users_sequence";
+
+ @Id
+ private long id;
+
+ private String firstName;
+
+ private String lastName;
+
+ private String email;
+
+ public User() { }
+
+ public User(String firstName, String lastName, String email) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "id=" + id +
+ ", firstName='" + firstName + '\'' +
+ ", lastName='" + lastName + '\'' +
+ ", email='" + email + '\'' +
+ '}';
+ }
+}
diff --git a/spring-boot/src/main/java/com/baeldung/mongodb/services/SequenceGeneratorService.java b/spring-boot/src/main/java/com/baeldung/mongodb/services/SequenceGeneratorService.java
new file mode 100755
index 0000000000..52ece27d63
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/mongodb/services/SequenceGeneratorService.java
@@ -0,0 +1,35 @@
+package com.baeldung.mongodb.services;
+
+import com.baeldung.mongodb.models.DatabaseSequence;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.stereotype.Service;
+
+import java.util.Objects;
+
+import static org.springframework.data.mongodb.core.FindAndModifyOptions.options;
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+
+@Service
+public class SequenceGeneratorService {
+
+
+ private MongoOperations mongoOperations;
+
+ @Autowired
+ public SequenceGeneratorService(MongoOperations mongoOperations) {
+ this.mongoOperations = mongoOperations;
+ }
+
+ public long generateSequence(String seqName) {
+
+ DatabaseSequence counter = mongoOperations.findAndModify(query(where("_id").is(seqName)),
+ new Update().inc("seq",1), options().returnNew(true).upsert(true),
+ DatabaseSequence.class);
+ return !Objects.isNull(counter) ? counter.getSeq() : 1;
+
+ }
+}
diff --git a/spring-boot/src/test/java/com/baeldung/mongodb/MongoDbAutoGeneratedFieldIntegrationTest.java b/spring-boot/src/test/java/com/baeldung/mongodb/MongoDbAutoGeneratedFieldIntegrationTest.java
new file mode 100644
index 0000000000..3430bca69a
--- /dev/null
+++ b/spring-boot/src/test/java/com/baeldung/mongodb/MongoDbAutoGeneratedFieldIntegrationTest.java
@@ -0,0 +1,36 @@
+package com.baeldung.mongodb;
+
+import com.baeldung.mongodb.daos.UserRepository;
+import com.baeldung.mongodb.models.User;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringRunner;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+
+@RunWith(SpringRunner.class)
+public class MongoDbAutoGeneratedFieldIntegrationTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ public void contextLoads() {}
+
+ @Test
+ public void givenUserObject_whenSave_thenCreateNewUser() {
+
+ User user = new User();
+ user.setFirstName("John");
+ user.setLastName("Doe");
+ user.setEmail("john.doe@example.com");
+ userRepository.save(user);
+
+ assertThat(userRepository.findAll().size()).isGreaterThan(0);
+ }
+
+
+}