diff --git a/core-java-modules/core-java-exceptions/pom.xml b/core-java-modules/core-java-exceptions/pom.xml
index 60c5e2650a..0778b6b5a3 100644
--- a/core-java-modules/core-java-exceptions/pom.xml
+++ b/core-java-modules/core-java-exceptions/pom.xml
@@ -29,6 +29,11 @@
${lombok.version}
provided
+
+ org.apache.commons
+ commons-lang3
+ ${commons.lang3.version}
+
org.assertj
@@ -40,6 +45,7 @@
1.5.0-b01
+ 3.10
3.10.0
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/Arithmetic.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/Arithmetic.java
new file mode 100644
index 0000000000..db29198b39
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/Arithmetic.java
@@ -0,0 +1,20 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Arithmetic {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(Arithmetic.class);
+
+ public static void main(String[] args) {
+
+ try {
+ int result = 30 / 0; // Trying to divide by zero
+ } catch (ArithmeticException e) {
+ LOGGER.error("ArithmeticException caught!");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ArrayIndexOutOfBounds.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ArrayIndexOutOfBounds.java
new file mode 100644
index 0000000000..54c95f224c
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ArrayIndexOutOfBounds.java
@@ -0,0 +1,24 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ArrayIndexOutOfBounds {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(ArrayIndexOutOfBounds.class);
+
+ public static void main(String[] args) {
+
+ int[] nums = new int[] { 1, 2, 3 };
+
+ try {
+ int numFromNegativeIndex = nums[-1]; // Trying to access at negative index
+ int numFromGreaterIndex = nums[4]; // Trying to access at greater index
+ int numFromLengthIndex = nums[3]; // Trying to access at index equal to size of the array
+ } catch (ArrayIndexOutOfBoundsException e) {
+ LOGGER.error("ArrayIndexOutOfBoundsException caught");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ClassCast.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ClassCast.java
new file mode 100644
index 0000000000..8f8a6cf9e6
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ClassCast.java
@@ -0,0 +1,36 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class Animal {
+
+}
+
+class Dog extends Animal {
+
+}
+
+class Lion extends Animal {
+
+}
+
+public class ClassCast {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(ClassCast.class);
+
+ public static void main(String[] args) {
+
+ try {
+ Animal animalOne = new Dog(); // At runtime the instance is dog
+ Dog bruno = (Dog) animalOne; // Downcasting
+
+ Animal animalTwo = new Lion(); // At runtime the instance is animal
+ Dog tommy = (Dog) animalTwo; // Downcasting
+ } catch (ClassCastException e) {
+ LOGGER.error("ClassCastException caught!");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/FileNotFound.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/FileNotFound.java
new file mode 100644
index 0000000000..a9f2e5ee84
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/FileNotFound.java
@@ -0,0 +1,25 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FileNotFound {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(FileNotFound.class);
+
+ public static void main(String[] args) {
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(new File("/invalid/file/location")));
+ } catch (FileNotFoundException e) {
+ LOGGER.error("FileNotFoundException caught!");
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/GlobalExceptionHandler.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/GlobalExceptionHandler.java
new file mode 100644
index 0000000000..f2e89f44e3
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/GlobalExceptionHandler.java
@@ -0,0 +1,28 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GlobalExceptionHandler {
+
+ public static void main(String[] args) {
+
+ Handler globalExceptionHandler = new Handler();
+ Thread.setDefaultUncaughtExceptionHandler(globalExceptionHandler);
+ new GlobalExceptionHandler().performArithmeticOperation(10, 0);
+ }
+
+ public int performArithmeticOperation(int num1, int num2) {
+ return num1/num2;
+ }
+
+}
+
+class Handler implements Thread.UncaughtExceptionHandler {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(Handler.class);
+
+ public void uncaughtException(Thread t, Throwable e) {
+ LOGGER.info("Unhandled exception caught!");
+ }
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/IllegalArgument.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/IllegalArgument.java
new file mode 100644
index 0000000000..d54757dfac
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/IllegalArgument.java
@@ -0,0 +1,18 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class IllegalArgument {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(IllegalArgument.class);
+
+ public static void main(String[] args) {
+ try {
+ Thread.sleep(-1000);
+ } catch (InterruptedException e) {
+ LOGGER.error("IllegalArgumentException caught!");
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/IllegalState.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/IllegalState.java
new file mode 100644
index 0000000000..0a812d2b82
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/IllegalState.java
@@ -0,0 +1,32 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class IllegalState {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(IllegalState.class);
+
+ public static void main(String[] args) {
+
+ List intList = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ intList.add(i);
+ }
+
+ Iterator intListIterator = intList.iterator(); // Initialized with index at -1
+
+ try {
+ intListIterator.remove(); // IllegalStateException
+ } catch (IllegalStateException e) {
+ LOGGER.error("IllegalStateException caught!");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/InterruptedExceptionExample.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/InterruptedExceptionExample.java
new file mode 100644
index 0000000000..d0c8bb2cd0
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/InterruptedExceptionExample.java
@@ -0,0 +1,28 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ChildThread extends Thread {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(ChildThread.class);
+
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ LOGGER.error("InterruptedException caught!");
+ }
+ }
+
+}
+
+public class InterruptedExceptionExample {
+
+ public static void main(String[] args) throws InterruptedException {
+ ChildThread childThread = new ChildThread();
+ childThread.start();
+ childThread.interrupt();
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/MalformedURL.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/MalformedURL.java
new file mode 100644
index 0000000000..9a02f005fd
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/MalformedURL.java
@@ -0,0 +1,25 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MalformedURL {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(MalformedURL.class);
+
+ public static void main(String[] args) {
+
+ URL baeldungURL = null;
+
+ try {
+ baeldungURL = new URL("malformedurl");
+ } catch (MalformedURLException e) {
+ LOGGER.error("MalformedURLException caught!");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/NullPointer.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/NullPointer.java
new file mode 100644
index 0000000000..445cbecdc8
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/NullPointer.java
@@ -0,0 +1,36 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NullPointer {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(NullPointer.class);
+
+ public static void main(String[] args) {
+
+ Person personObj = null;
+
+ try {
+ String name = personObj.personName; // Accessing the field of a null object
+ personObj.personName = "Jon Doe"; // Modifying the field of a null object
+ } catch (NullPointerException e) {
+ LOGGER.error("NullPointerException caught!");
+ }
+
+ }
+}
+
+class Person {
+
+ public String personName;
+
+ public String getPersonName() {
+ return personName;
+ }
+
+ public void setPersonName(String personName) {
+ this.personName = personName;
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/NumberFormat.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/NumberFormat.java
new file mode 100644
index 0000000000..576fe51f78
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/NumberFormat.java
@@ -0,0 +1,23 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NumberFormat {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(NumberFormat.class);
+
+ public static void main(String[] args) {
+
+ String str1 = "100ABCD";
+
+ try {
+ int x = Integer.parseInt(str1); // Converting string with inappropriate format
+ int y = Integer.valueOf(str1);
+ } catch (NumberFormatException e) {
+ LOGGER.error("NumberFormatException caught!");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ParseExceptionExample.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ParseExceptionExample.java
new file mode 100644
index 0000000000..e3b3e04b10
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/ParseExceptionExample.java
@@ -0,0 +1,25 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ParseExceptionExample {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(ParseExceptionExample.class);
+
+ public static void main(String[] args) {
+
+ DateFormat format = new SimpleDateFormat("MM, dd, yyyy");
+
+ try {
+ format.parse("01, , 2010");
+ } catch (ParseException e) {
+ LOGGER.error("ParseException caught!");
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/StringIndexOutOfBounds.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/StringIndexOutOfBounds.java
new file mode 100644
index 0000000000..0ee132e568
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/globalexceptionhandler/StringIndexOutOfBounds.java
@@ -0,0 +1,23 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StringIndexOutOfBounds {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(StringIndexOutOfBounds.class);
+
+ public static void main(String[] args) {
+
+ String str = "Hello World";
+
+ try {
+ char charAtNegativeIndex = str.charAt(-1); // Trying to access at negative index
+ char charAtLengthIndex = str.charAt(11); // Trying to access at index equal to size of the string
+ } catch (StringIndexOutOfBoundsException e) {
+ LOGGER.error("StringIndexOutOfBoundsException caught");
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/rootcausefinder/RootCauseFinder.java b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/rootcausefinder/RootCauseFinder.java
new file mode 100644
index 0000000000..06610f3874
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/main/java/com/baeldung/exceptions/rootcausefinder/RootCauseFinder.java
@@ -0,0 +1,95 @@
+package com.baeldung.exceptions.rootcausefinder;
+
+import java.time.LocalDate;
+import java.time.Period;
+import java.time.format.DateTimeParseException;
+import java.util.Objects;
+
+/**
+ * Utility class to find root cause exceptions.
+ */
+public class RootCauseFinder {
+
+ public static Throwable findCauseUsingPlainJava(Throwable throwable) {
+ Objects.requireNonNull(throwable);
+ Throwable rootCause = throwable;
+ while (rootCause.getCause() != null) {
+ rootCause = rootCause.getCause();
+ }
+ return rootCause;
+ }
+
+ /**
+ * Calculates the age of a person from a given date.
+ */
+ static class AgeCalculator {
+
+ private AgeCalculator() {
+ }
+
+ public static int calculateAge(String birthDate) throws CalculationException {
+ if (birthDate == null || birthDate.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+
+ try {
+ return Period
+ .between(parseDate(birthDate), LocalDate.now())
+ .getYears();
+ } catch (DateParseException ex) {
+ throw new CalculationException(ex);
+ }
+ }
+
+ private static LocalDate parseDate(String birthDateAsString) throws DateParseException {
+
+ LocalDate birthDate;
+ try {
+ birthDate = LocalDate.parse(birthDateAsString);
+ } catch (DateTimeParseException ex) {
+ throw new InvalidFormatException(birthDateAsString, ex);
+ }
+
+ if (birthDate.isAfter(LocalDate.now())) {
+ throw new DateOutOfRangeException(birthDateAsString);
+ }
+
+ return birthDate;
+ }
+
+ }
+
+ static class CalculationException extends Exception {
+
+ CalculationException(DateParseException ex) {
+ super(ex);
+ }
+ }
+
+ static class DateParseException extends Exception {
+
+ DateParseException(String input) {
+ super(input);
+ }
+
+ DateParseException(String input, Throwable thr) {
+ super(input, thr);
+ }
+ }
+
+ static class InvalidFormatException extends DateParseException {
+
+ InvalidFormatException(String input, Throwable thr) {
+ super("Invalid date format: " + input, thr);
+ }
+ }
+
+ static class DateOutOfRangeException extends DateParseException {
+
+ DateOutOfRangeException(String date) {
+ super("Date out of range: " + date);
+ }
+
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/test/java/com/baeldung/exceptions/globalexceptionhandler/GlobalExceptionHandlerUnitTest.java b/core-java-modules/core-java-exceptions/src/test/java/com/baeldung/exceptions/globalexceptionhandler/GlobalExceptionHandlerUnitTest.java
new file mode 100644
index 0000000000..74ceb3b442
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/test/java/com/baeldung/exceptions/globalexceptionhandler/GlobalExceptionHandlerUnitTest.java
@@ -0,0 +1,64 @@
+package com.baeldung.exceptions.globalexceptionhandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.slf4j.LoggerFactory;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.Appender;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GlobalExceptionHandlerUnitTest {
+
+ @Mock
+ private Appender mockAppender;
+
+ @Captor
+ private ArgumentCaptor captorLoggingEvent;
+
+ @Before
+ public void setup() {
+ final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ logger.addAppender(mockAppender);
+
+ Handler globalExceptionHandler = new Handler();
+ Thread.setDefaultUncaughtExceptionHandler(globalExceptionHandler);
+ }
+
+ @After
+ public void teardown() {
+ final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ logger.detachAppender(mockAppender);
+ }
+
+ @Test
+ public void whenArithmeticException_thenUseUncaughtExceptionHandler() throws InterruptedException {
+
+ Thread globalExceptionHandlerThread = new Thread() {
+ public void run() {
+ GlobalExceptionHandler globalExceptionHandlerObj = new GlobalExceptionHandler();
+ globalExceptionHandlerObj.performArithmeticOperation(99, 0);
+ }
+ };
+
+ globalExceptionHandlerThread.start();
+ globalExceptionHandlerThread.join();
+
+ verify(mockAppender).doAppend(captorLoggingEvent.capture());
+ LoggingEvent loggingEvent = captorLoggingEvent.getValue();
+
+ assertThat(loggingEvent.getLevel()).isEqualTo(Level.INFO);
+ assertThat(loggingEvent.getFormattedMessage()).isEqualTo("Unhandled exception caught!");
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions/src/test/java/com/baeldung/exceptions/rootcausefinder/RootCauseFinderUnitTest.java b/core-java-modules/core-java-exceptions/src/test/java/com/baeldung/exceptions/rootcausefinder/RootCauseFinderUnitTest.java
new file mode 100644
index 0000000000..f42388857a
--- /dev/null
+++ b/core-java-modules/core-java-exceptions/src/test/java/com/baeldung/exceptions/rootcausefinder/RootCauseFinderUnitTest.java
@@ -0,0 +1,99 @@
+package com.baeldung.exceptions.rootcausefinder;
+
+import com.baeldung.exceptions.rootcausefinder.RootCauseFinder.CalculationException;
+import com.baeldung.exceptions.rootcausefinder.RootCauseFinder.DateOutOfRangeException;
+import com.google.common.base.Throwables;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
+
+import static com.baeldung.exceptions.rootcausefinder.RootCauseFinder.AgeCalculator;
+import static com.baeldung.exceptions.rootcausefinder.RootCauseFinder.findCauseUsingPlainJava;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests the {@link RootCauseFinder}.
+ */
+public class RootCauseFinderUnitTest {
+
+ @Test
+ public void givenBirthDate_whenCalculatingAge_thenAgeReturned() {
+ try {
+ int age = AgeCalculator.calculateAge("1990-01-01");
+ Assertions.assertEquals(1990, LocalDate
+ .now()
+ .minus(age, ChronoUnit.YEARS)
+ .getYear());
+ } catch (CalculationException e) {
+ Assertions.fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void givenWrongFormatDate_whenFindingRootCauseUsingJava_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge("010102");
+ } catch (CalculationException ex) {
+ assertTrue(findCauseUsingPlainJava(ex) instanceof DateTimeParseException);
+ }
+ }
+
+ @Test
+ public void givenOutOfRangeDate_whenFindingRootCauseUsingJava_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge("2020-04-04");
+ } catch (CalculationException ex) {
+ assertTrue(findCauseUsingPlainJava(ex) instanceof DateOutOfRangeException);
+ }
+ }
+
+ @Test
+ public void givenNullDate_whenFindingRootCauseUsingJava_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge(null);
+ } catch (Exception ex) {
+ assertTrue(findCauseUsingPlainJava(ex) instanceof IllegalArgumentException);
+ }
+ }
+
+ @Test
+ public void givenWrongFormatDate_whenFindingRootCauseUsingApacheCommons_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge("010102");
+ } catch (CalculationException ex) {
+ assertTrue(ExceptionUtils.getRootCause(ex) instanceof DateTimeParseException);
+ }
+ }
+
+ @Test
+ public void givenOutOfRangeDate_whenFindingRootCauseUsingApacheCommons_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge("2020-04-04");
+ } catch (CalculationException ex) {
+ assertTrue(ExceptionUtils.getRootCause(ex) instanceof DateOutOfRangeException);
+ }
+ }
+
+ @Test
+ public void givenWrongFormatDate_whenFindingRootCauseUsingGuava_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge("010102");
+ } catch (CalculationException ex) {
+ assertTrue(Throwables.getRootCause(ex) instanceof DateTimeParseException);
+ }
+ }
+
+ @Test
+ public void givenOutOfRangeDate_whenFindingRootCauseUsingGuava_thenRootCauseFound() {
+ try {
+ AgeCalculator.calculateAge("2020-04-04");
+ } catch (CalculationException ex) {
+ assertTrue(Throwables.getRootCause(ex) instanceof DateOutOfRangeException);
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/exceptions/RootCauseFinder.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/exceptions/RootCauseFinder.java
new file mode 100644
index 0000000000..e05dc7a6cd
--- /dev/null
+++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/exceptions/RootCauseFinder.java
@@ -0,0 +1,95 @@
+package com.baeldung.exceptions;
+
+import java.time.LocalDate;
+import java.time.Period;
+import java.time.format.DateTimeParseException;
+import java.util.Objects;
+
+/**
+ * Utility class to find root cause exceptions.
+ */
+public class RootCauseFinder {
+
+ public static Throwable findCauseUsingPlainJava(Throwable throwable) {
+ Objects.requireNonNull(throwable);
+ Throwable rootCause = throwable;
+ while (rootCause.getCause() != null) {
+ rootCause = rootCause.getCause();
+ }
+ return rootCause;
+ }
+
+ /**
+ * Calculates the age of a person from a given date.
+ */
+ static class AgeCalculator {
+
+ private AgeCalculator() {
+ }
+
+ public static int calculateAge(String birthDate) throws CalculationException {
+ if (birthDate == null || birthDate.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+
+ try {
+ return Period
+ .between(parseDate(birthDate), LocalDate.now())
+ .getYears();
+ } catch (DateParseException ex) {
+ throw new CalculationException(ex);
+ }
+ }
+
+ private static LocalDate parseDate(String birthDateAsString) throws DateParseException {
+
+ LocalDate birthDate;
+ try {
+ birthDate = LocalDate.parse(birthDateAsString);
+ } catch (DateTimeParseException ex) {
+ throw new InvalidFormatException(birthDateAsString, ex);
+ }
+
+ if (birthDate.isAfter(LocalDate.now())) {
+ throw new DateOutOfRangeException(birthDateAsString);
+ }
+
+ return birthDate;
+ }
+
+ }
+
+ static class CalculationException extends Exception {
+
+ CalculationException(DateParseException ex) {
+ super(ex);
+ }
+ }
+
+ static class DateParseException extends Exception {
+
+ DateParseException(String input) {
+ super(input);
+ }
+
+ DateParseException(String input, Throwable thr) {
+ super(input, thr);
+ }
+ }
+
+ static class InvalidFormatException extends DateParseException {
+
+ InvalidFormatException(String input, Throwable thr) {
+ super("Invalid date format: " + input, thr);
+ }
+ }
+
+ static class DateOutOfRangeException extends DateParseException {
+
+ DateOutOfRangeException(String date) {
+ super("Date out of range: " + date);
+ }
+
+ }
+
+}