diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/build.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/build.gradle
new file mode 100644
index 0000000000..355744da86
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/build.gradle
@@ -0,0 +1,29 @@
+plugins {
+ id 'java'
+}
+
+group = "com.baeldung.gradle"
+version = "1.0.0-SNAPSHOT"
+sourceCompatibility = JavaVersion.VERSION_17
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {
+ name = "GitHubPackages"
+ url = "https://maven.pkg.github.com/eugenp/tutorials"
+ credentials {
+ username = project.USERNAME
+ password = project.GITHUB_TOKEN
+ }
+ }
+}
+
+dependencies {
+ implementation('com.baeldung.gradle:publish-package:1.0.0-SNAPSHOT')
+ testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.0")
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
+}
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.jar b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..41d9927a4d
Binary files /dev/null and b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.properties b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..070cb702f0
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew
new file mode 100755
index 0000000000..1b6c787337
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew.bat b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew.bat
new file mode 100644
index 0000000000..107acd32c4
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/settings.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/settings.gradle
new file mode 100644
index 0000000000..0177937493
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = "multiple-repositories"
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/main/java/com/baeldung/gradle/multiplerepositories/Student.java b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/main/java/com/baeldung/gradle/multiplerepositories/Student.java
new file mode 100644
index 0000000000..dcb28a3742
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/main/java/com/baeldung/gradle/multiplerepositories/Student.java
@@ -0,0 +1,26 @@
+package com.baeldung.gradle.multiplerepositories;
+
+import com.baeldung.gradle.publishPackage.User;
+
+public class Student extends User {
+
+ private String studentCode;
+
+ private String lastInstitution;
+
+ public String getStudentCode() {
+ return studentCode;
+ }
+
+ public void setStudentCode(String studentCode) {
+ this.studentCode = studentCode;
+ }
+
+ public String getLastInstitution() {
+ return lastInstitution;
+ }
+
+ public void setLastInstitution(String lastInstitution) {
+ this.lastInstitution = lastInstitution;
+ }
+}
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/test/java/com/baeldung/gradle/multiplerepositories/MultipleRepositoryTest.java b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/test/java/com/baeldung/gradle/multiplerepositories/MultipleRepositoryTest.java
new file mode 100644
index 0000000000..d0a72d9b3c
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/test/java/com/baeldung/gradle/multiplerepositories/MultipleRepositoryTest.java
@@ -0,0 +1,19 @@
+package com.baeldung.gradle.multiplerepositories;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class MultipleRepositoryTest {
+
+ @Test
+ public void testPublishedPackage() {
+ Student student = new Student();
+ student.setId(1);
+ student.setStudentCode("CD-875");
+ student.setName("John Doe");
+ student.setLastInstitution("Institute of Technology");
+
+ assertEquals("John Doe", student.getName());
+ }
+}
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/build.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/build.gradle
new file mode 100644
index 0000000000..bd97650e9c
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/build.gradle
@@ -0,0 +1,33 @@
+plugins {
+ id "maven-publish"
+ id "java"
+}
+
+group = "com.baeldung.gradle"
+version = "1.0.0-SNAPSHOT"
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+publishing {
+ publications {
+ register("jar", MavenPublication) {
+ from(components["java"])
+ pom {
+ url.set("https://github.com/eugenp/tutorials.git")
+ }
+ }
+ }
+ repositories {
+ maven {
+ name = "GitHubPackages"
+ url = "https://maven.pkg.github.com/eugenp/tutorials"
+ credentials {
+ username = project.USERNAME
+ password = project.GITHUB_TOKEN
+ }
+ }
+ }
+}
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.jar b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..41d9927a4d
Binary files /dev/null and b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.properties b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..070cb702f0
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew
new file mode 100755
index 0000000000..1b6c787337
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew.bat b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew.bat
new file mode 100644
index 0000000000..107acd32c4
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/settings.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/settings.gradle
new file mode 100644
index 0000000000..9ccd121d0d
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = "publish-package"
diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/src/main/java/com/baeldung/gradle/publish_package/User.java b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/src/main/java/com/baeldung/gradle/publish_package/User.java
new file mode 100644
index 0000000000..bc612d038b
--- /dev/null
+++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/src/main/java/com/baeldung/gradle/publish_package/User.java
@@ -0,0 +1,35 @@
+package com.baeldung.gradle.publish_package;
+
+import java.util.Date;
+
+public class User {
+ private Integer id;
+
+ private String name;
+
+ private Date dob;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getDob() {
+ return dob;
+ }
+
+ public void setDob(Date dob) {
+ this.dob = dob;
+ }
+}
diff --git a/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-fullpath-job b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-fullpath-job
new file mode 100644
index 0000000000..98dc32efe5
--- /dev/null
+++ b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-fullpath-job
@@ -0,0 +1,11 @@
+pipeline {
+ agent any
+ stages {
+ stage('Build') {
+ steps {
+ dir('/var/jenkins_home/workspace/SamplePipeline/scripts') {
+ }
+ }
+ }
+ }
+}
diff --git a/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-job b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-job
new file mode 100644
index 0000000000..42a946ca49
--- /dev/null
+++ b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-job
@@ -0,0 +1,11 @@
+ pipeline {
+ agent any
+ stages {
+ stage('Build') {
+ steps {
+ dir('scripts') {
+ }
+ }
+ }
+ }
+}
diff --git a/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-sh-job b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-sh-job
new file mode 100644
index 0000000000..9b7f992b86
--- /dev/null
+++ b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-sh-job
@@ -0,0 +1,10 @@
+pipeline {
+ agent any
+ stages {
+ stage('Build') {
+ steps {
+ sh 'cd scripts'
+ }
+ }
+ }
+}
diff --git a/jenkins-modules/jenkins-jobs/dsl-error-fix/no-such-dsl-method-job b/jenkins-modules/jenkins-jobs/dsl-error-fix/no-such-dsl-method-job
new file mode 100644
index 0000000000..f68cb70c2f
--- /dev/null
+++ b/jenkins-modules/jenkins-jobs/dsl-error-fix/no-such-dsl-method-job
@@ -0,0 +1,10 @@
+pipeline {
+ agent any
+ stages {
+ stage('Build') {
+ steps {
+ mvn 'clean install'
+ }
+ }
+ }
+}
diff --git a/jersey/pom.xml b/jersey/pom.xml
index c8a7de66ae..7c7330d84f 100644
--- a/jersey/pom.xml
+++ b/jersey/pom.xml
@@ -72,6 +72,11 @@
${jersey.version}
test
+
+ org.glassfish.jersey.connectors
+ jersey-apache-connector
+ ${jersey.version}
+
@@ -95,7 +100,7 @@
- 2.26
+ 2.38
\ No newline at end of file
diff --git a/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java b/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java
index ebcbe1d4ab..29db298e9e 100644
--- a/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java
+++ b/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java
@@ -1,6 +1,8 @@
package com.baeldung.jersey.client;
import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
+import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
+import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.client.oauth1.AccessToken;
import org.glassfish.jersey.client.oauth1.ConsumerCredentials;
@@ -155,7 +157,8 @@ public class JerseyClientHeaders {
}
public static Response sendRestrictedHeaderThroughDefaultTransportConnector(String headerKey, String headerValue) {
- Client client = ClientBuilder.newClient();
+ ClientConfig clientConfig = new ClientConfig().connectorProvider(new ApacheConnectorProvider());
+ Client client = ClientBuilder.newClient(clientConfig);
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
return client.target(TARGET)
diff --git a/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java b/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java
index cca7446dd1..24552f6d81 100644
--- a/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java
+++ b/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java
@@ -2,10 +2,8 @@ package com.baeldung.jersey.server;
import com.baeldung.jersey.client.JerseyClientHeaders;
import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
-import org.glassfish.jersey.media.sse.SseFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
-import org.junit.Ignore;
import org.junit.Test;
import javax.ws.rs.core.Application;
@@ -17,7 +15,6 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
public class EchoHeadersUnitTest extends JerseyTest {
private static final String SIMPLE_HEADER_KEY = "my-header-key";
diff --git a/lightrun/tasks-service/pom.xml b/lightrun/tasks-service/pom.xml
index 2b3b37e51b..c3542b0089 100644
--- a/lightrun/tasks-service/pom.xml
+++ b/lightrun/tasks-service/pom.xml
@@ -24,6 +24,10 @@
org.springframework.boot
spring-boot-starter-artemis
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
org.springframework.boot
spring-boot-starter-data-jpa
diff --git a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/SimpleCacheCustomizer.java b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/SimpleCacheCustomizer.java
new file mode 100644
index 0000000000..bf8bb6ad6e
--- /dev/null
+++ b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/SimpleCacheCustomizer.java
@@ -0,0 +1,16 @@
+package com.baeldung.tasksservice;
+
+import java.util.List;
+
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SimpleCacheCustomizer implements CacheManagerCustomizer {
+
+ @Override
+ public void customize(ConcurrentMapCacheManager cacheManager) {
+ cacheManager.setCacheNames(List.of("tasks"));
+ }
+}
\ No newline at end of file
diff --git a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java
index dfd9859674..84a8ed7967 100644
--- a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java
+++ b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java
@@ -2,8 +2,10 @@ package com.baeldung.tasksservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
+@EnableCaching
public class TasksServiceApplication {
public static void main(String[] args) {
diff --git a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java
index 3539dbbc3c..107bf56bb9 100644
--- a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java
+++ b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java
@@ -19,6 +19,7 @@ import java.util.UUID;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.baeldung.tasksservice.adapters.repository.TaskRecord;
@@ -29,6 +30,7 @@ public class TasksService {
@Autowired
private TasksRepository tasksRepository;
+ @Cacheable("tasks")
public TaskRecord getTaskById(String id) {
return tasksRepository.findById(id)
.orElseThrow(() -> new UnknownTaskException(id));
diff --git a/logging-modules/logback/pom.xml b/logging-modules/logback/pom.xml
index ab778e954e..937a88da09 100644
--- a/logging-modules/logback/pom.xml
+++ b/logging-modules/logback/pom.xml
@@ -24,6 +24,11 @@
logback-classic
${logback.version}
+
+ ch.qos.logback
+ logback-core
+ ${logback.version}
+
ch.qos.logback.contrib
logback-json-classic
@@ -34,6 +39,11 @@
logback-jackson
${logback.contrib.version}
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
com.fasterxml.jackson.core
jackson-databind
@@ -55,13 +65,13 @@
- javax.mail
- mail
- ${javax.mail.version}
+ com.sun.mail
+ javax.mail
+ ${javax.mail.version}
javax.activation
- activation
+ javax.activation-api
${javax.activation.version}
runtime
@@ -106,8 +116,10 @@
20180130
0.1.5
3.3.5
- 1.4.7
- 1.1.1
+ 1.6.2
+ 1.2.0
+ 1.3.5
+ 2.0.4
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java
index 9ec14884f4..726764b411 100644
--- a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java
+++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java
@@ -14,6 +14,9 @@ public interface ArticleRepository extends JpaRepository {
List findAllByPublicationTimeBetween(Date publicationTimeStart,
Date publicationTimeEnd);
+ Article findByPublicationTimeBetween(Date publicationTimeStart,
+ Date publicationTimeEnd);
+
@Query("select a from Article a where a.creationDateTime <= :creationDateTime")
List findAllWithCreationDateTimeBefore(
@Param("creationDateTime") Date creationDateTime);
diff --git a/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/query/nonuniqueresultexception/NonUniqueResultExceptionIntegrationTest.java b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/query/nonuniqueresultexception/NonUniqueResultExceptionIntegrationTest.java
new file mode 100644
index 0000000000..b032819ad6
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/query/nonuniqueresultexception/NonUniqueResultExceptionIntegrationTest.java
@@ -0,0 +1,35 @@
+package com.baeldung.spring.data.jpa.query.nonuniqueresultexception;
+
+import com.baeldung.spring.data.jpa.query.datetime.ArticleRepository;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.persistence.NonUniqueResultException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+@RunWith(SpringRunner.class)
+@DataJpaTest(properties = "spring.sql.init.data-locations=classpath:import_entities.sql", showSql = false)
+public class NonUniqueResultExceptionIntegrationTest {
+
+ @Autowired
+ private ArticleRepository repository;
+
+ @Test
+ public void givenImportedArticles_whenFindByPublicationTimeBetween_thenIncorrectResultSizeDataAccessExceptionThrown() {
+ assertThatThrownBy(() -> repository.findByPublicationTimeBetween(new SimpleDateFormat("HH:mm").parse("15:15"), new SimpleDateFormat("HH:mm").parse("16:30")))
+ .isInstanceOf(IncorrectResultSizeDataAccessException.class)
+ .hasCauseInstanceOf(NonUniqueResultException.class);
+ }
+
+ @Test
+ public void givenImportedArticles_whenFindAllByPublicationTimeBetween_thenSuccess() throws ParseException {
+ repository.findAllByPublicationTimeBetween(new SimpleDateFormat("HH:mm").parse("15:15"), new SimpleDateFormat("HH:mm").parse("16:30"));
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/pom.xml b/persistence-modules/spring-data-jpa-repo-2/pom.xml
index 9240a00ee2..dd0406eca8 100644
--- a/persistence-modules/spring-data-jpa-repo-2/pom.xml
+++ b/persistence-modules/spring-data-jpa-repo-2/pom.xml
@@ -1,7 +1,7 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
spring-data-jpa-repo-2
spring-data-jpa-repo-2
@@ -34,6 +34,14 @@
com.h2database
h2
+
+ com.querydsl
+ querydsl-apt
+
+
+ com.querydsl
+ querydsl-jpa
+
com.google.guava
guava
@@ -41,4 +49,53 @@
+
+
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+ generate-sources
+
+ process
+
+
+ ${project.build.directory}/generated-sources
+ com.querydsl.apt.jpa.JPAAnnotationProcessor
+
+
+
+
+
+ org.bsc.maven
+ maven-processor-plugin
+ 3.3.3
+
+
+ process
+
+ process
+
+ generate-sources
+
+ ${project.build.directory}/generated-sources
+
+ org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor
+
+
+
+
+
+
+ org.hibernate
+ hibernate-jpamodelgen
+ 5.6.11.Final
+
+
+
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/model/Employee.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/model/Employee.java
new file mode 100644
index 0000000000..9690bcf68a
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/model/Employee.java
@@ -0,0 +1,76 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference.model;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "employee")
+@NamedQuery(name = "Employee.findById", query = "SELECT e FROM Employee e WHERE e.id = :id")
+public class Employee implements Serializable {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ @Column(nullable = false)
+ private String firstName;
+ @Column(nullable = false)
+ private String lastName;
+
+ @Column(nullable = false)
+ private String 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 boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Employee employee = (Employee) o;
+ return Objects.equals(id, employee.id) && Objects.equals(firstName, employee.firstName) && Objects.equals(lastName, employee.lastName) && Objects.equals(email, employee.email);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, firstName, lastName, email);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/config/SpringDataJpaConfig.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/config/SpringDataJpaConfig.java
new file mode 100644
index 0000000000..57f9d2981f
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/config/SpringDataJpaConfig.java
@@ -0,0 +1,66 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference.springdata.config;
+
+import java.util.Properties;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.JpaVendorAdapter;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository.EmployeeRepository;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(basePackageClasses = EmployeeRepository.class)
+public class SpringDataJpaConfig {
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
+ LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
+ em.setDataSource(dataSource);
+ em.setPackagesToScan("com.baeldung.spring.data.persistence.springdata_jpa_difference.model");
+
+ JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ em.setJpaVendorAdapter(vendorAdapter);
+
+ Properties properties = new Properties();
+ properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
+ properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
+
+ em.setJpaProperties(properties);
+
+ return em;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
+ JpaTransactionManager transactionManager = new JpaTransactionManager();
+ transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
+ return transactionManager;
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ return DataSourceBuilder.create()
+ .url("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1")
+ .driverClassName("org.h2.Driver")
+ .username("sa")
+ .password("sa")
+ .build();
+ }
+
+ @Bean
+ public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+ return new JPAQueryFactory((entityManager));
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepository.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepository.java
new file mode 100644
index 0000000000..012a46d885
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepository.java
@@ -0,0 +1,19 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository;
+
+import java.util.List;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee;
+
+@Repository
+public interface EmployeeRepository extends JpaRepository {
+
+ List findByFirstName(String firstName);
+
+ @Query(value = "SELECT e FROM Employee e")
+ List findAllEmployee(Sort sort);
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepositoryPagingAndSort.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepositoryPagingAndSort.java
new file mode 100644
index 0000000000..731735ea62
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepositoryPagingAndSort.java
@@ -0,0 +1,11 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee;
+
+@Repository
+public interface EmployeeRepositoryPagingAndSort extends PagingAndSortingRepository {
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/JpaDaoIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/JpaDaoIntegrationTest.java
new file mode 100644
index 0000000000..b25038f175
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/JpaDaoIntegrationTest.java
@@ -0,0 +1,201 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference;
+
+import static com.baeldung.spring.data.persistence.springdatajpadifference.TestUtils.employee;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.CriteriaUpdate;
+import javax.persistence.criteria.Root;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee;
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee_;
+
+public class JpaDaoIntegrationTest {
+
+ private final EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu-test");
+ private final EntityManager entityManager = emf.createEntityManager();
+
+ @Before
+ public void setup() {
+ deleteAllEmployees();
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ assertEquals(employee, entityManager.find(Employee.class, employee.getId()));
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindByIdCriteriaQuery_thenEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Employee.class);
+ Root root = criteriaQuery.from(Employee.class);
+ criteriaQuery.select(root);
+
+ criteriaQuery.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId()));
+
+ assertEquals(employee, entityManager.createQuery(criteriaQuery)
+ .getSingleResult());
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindByIdJpql_thenEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ Query jpqlQuery = entityManager.createQuery("SELECT e from Employee e WHERE e.id=:id");
+ jpqlQuery.setParameter("id", employee.getId());
+
+ assertEquals(employee, jpqlQuery.getSingleResult());
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindByIdNamedQuery_thenEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ Query query = entityManager.createNamedQuery("Employee.findById");
+
+ query.setParameter(Employee_.ID, employee.getId());
+
+ assertEquals(employee, query.getSingleResult());
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindWithPaginationAndSort_thenEmployeesAreFound() {
+ Employee john = employee("John", "Doe");
+ Employee bob = employee("Bob", "Smith");
+ Employee frank = employee("Frank", "Brown");
+ Employee james = employee("James", "Smith");
+ save(john);
+ save(bob);
+ save(frank);
+ save(james);
+
+ CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Employee.class);
+ Root root = criteriaQuery.from(Employee.class);
+ criteriaQuery.select(root);
+ criteriaQuery.orderBy(criteriaBuilder.asc(root.get(Employee_.FIRST_NAME)));
+
+ TypedQuery query = entityManager.createQuery(criteriaQuery);
+
+ query.setFirstResult(0);
+ query.setMaxResults(3);
+
+ List employeeList = query.getResultList();
+
+ assertEquals(Arrays.asList(bob, frank, james), employeeList);
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenUpdateEmployeeEmail_thenEmployeeHasUpdatedEmail() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ Employee employeeToUpdate = entityManager.find(Employee.class, employee.getId());
+
+ String updatedEmail = "email@gmail.com";
+
+ employeeToUpdate.setEmail(updatedEmail);
+
+ update(employeeToUpdate);
+
+ assertEquals(updatedEmail, entityManager.find(Employee.class, employee.getId())
+ .getEmail());
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenUpdateEmployeeEmailWithCriteria_thenEmployeeHasUpdatedEmail() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ String updatedEmail = "email@gmail.com";
+
+ CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
+ CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate(Employee.class);
+ Root root = criteriaUpdate.from(Employee.class);
+
+ criteriaUpdate.set(Employee_.EMAIL, updatedEmail);
+ criteriaUpdate.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId()));
+
+ assertEquals(1, update(criteriaUpdate));
+
+ assertEquals(updatedEmail, entityManager.find(Employee.class, employee.getId())
+ .getEmail());
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenRemoveEmployee_thenNoEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+ save(employee);
+
+ delete(employee.getId());
+
+ assertNull(entityManager.find(Employee.class, employee.getId()));
+ }
+
+ private void deleteAllEmployees() {
+ entityManager.getTransaction()
+ .begin();
+ entityManager.createNativeQuery("DELETE from Employee")
+ .executeUpdate();
+ entityManager.getTransaction()
+ .commit();
+ }
+
+ public void save(Employee entity) {
+ entityManager.getTransaction()
+ .begin();
+ entityManager.persist(entity);
+ entityManager.getTransaction()
+ .commit();
+ }
+
+ public void update(Employee entity) {
+ entityManager.getTransaction()
+ .begin();
+ entityManager.merge(entity);
+ entityManager.getTransaction()
+ .commit();
+ }
+
+ public void delete(Long employee) {
+ entityManager.getTransaction()
+ .begin();
+ entityManager.remove(entityManager.find(Employee.class, employee));
+ entityManager.getTransaction()
+ .commit();
+ }
+
+ public int update(CriteriaUpdate criteriaUpdate) {
+ entityManager.getTransaction()
+ .begin();
+ int result = entityManager.createQuery(criteriaUpdate)
+ .executeUpdate();
+ entityManager.getTransaction()
+ .commit();
+ entityManager.clear();
+
+ return result;
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/SpringDataJpaIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/SpringDataJpaIntegrationTest.java
new file mode 100644
index 0000000000..e6febdc9f1
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/SpringDataJpaIntegrationTest.java
@@ -0,0 +1,153 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference;
+
+import static com.baeldung.spring.data.persistence.springdatajpadifference.TestUtils.employee;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee;
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.QEmployee;
+import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.config.SpringDataJpaConfig;
+import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository.EmployeeRepository;
+import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository.EmployeeRepositoryPagingAndSort;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+
+@ContextConfiguration(classes = SpringDataJpaConfig.class)
+@RunWith(SpringJUnit4ClassRunner.class)
+@Transactional
+@Rollback
+public class SpringDataJpaIntegrationTest {
+
+ @Autowired
+ private EmployeeRepository employeeRepository;
+
+ @Autowired
+ private EmployeeRepositoryPagingAndSort employeeRepositoryPagingAndSort;
+
+ @Autowired
+ private JPAQueryFactory jpaQueryFactory;
+
+ @Test
+ public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+
+ employeeRepository.save(employee);
+
+ assertEquals(Optional.of(employee), employeeRepository.findById(employee.getId()));
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindByFirstName_thenEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+
+ employeeRepository.save(employee);
+
+ assertEquals(employee, employeeRepository.findByFirstName(employee.getFirstName())
+ .get(0));
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenUpdateEmployeeEmail_thenEmployeeHasUpdatedEmail() {
+ Employee employee = employee("John", "Doe");
+
+ employeeRepository.save(employee);
+
+ Employee employeeToUpdate = employeeRepository.findById(employee.getId())
+ .orElse(null);
+
+ assertNotNull(employeeToUpdate);
+ assertEquals(employee, employeeToUpdate);
+
+ String updatedEmail = "email@gmail.com";
+
+ employeeToUpdate.setEmail(updatedEmail);
+
+ employeeRepository.save(employeeToUpdate);
+
+ assertEquals(Optional.of(employeeToUpdate), employeeRepository.findById(employee.getId()));
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenRemoveEmployee_thenNoEmployeeIsFound() {
+ Employee employee = employee("John", "Doe");
+
+ employeeRepository.save(employee);
+
+ Employee persistedEmployee = employeeRepository.findById(employee.getId())
+ .orElse(null);
+
+ assertNotNull(persistedEmployee);
+
+ employeeRepository.delete(persistedEmployee);
+
+ assertFalse(employeeRepository.findById(employee.getId())
+ .isPresent());
+ }
+
+ @Test
+ public void givenPersistedEmployees_whenFindSortedByFirstName_thenEmployeeAreFoundInOrder() {
+ Employee john = employee("John", "Doe");
+ Employee bob = employee("Bob", "Smith");
+ Employee frank = employee("Frank", "Brown");
+
+ employeeRepository.saveAll(Arrays.asList(john, bob, frank));
+
+ List employees = employeeRepository.findAllEmployee(Sort.by("firstName"));
+
+ assertEquals(3, employees.size());
+ assertEquals(bob, employees.get(0));
+ assertEquals(frank, employees.get(1));
+ assertEquals(john, employees.get(2));
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindByQueryDsl_thenEmployeeIsFound() {
+ Employee john = employee("John", "Doe");
+ Employee frank = employee("Frank", "Doe");
+
+ employeeRepository.saveAll(Arrays.asList(john, frank));
+
+ QEmployee employeePath = QEmployee.employee;
+
+ List employees = jpaQueryFactory.selectFrom(employeePath)
+ .where(employeePath.firstName.eq("John"), employeePath.lastName.eq("Doe"))
+ .fetch();
+
+ assertEquals(1, employees.size());
+ assertEquals(john, employees.get(0));
+ }
+
+ @Test
+ public void givenPersistedEmployee_whenFindBySortAndPagingRepository_thenEmployeeAreFound() {
+ Employee john = employee("John", "Doe");
+ Employee bob = employee("Bob", "Smith");
+ Employee frank = employee("Frank", "Brown");
+ Employee jimmy = employee("Jimmy", "Armstrong");
+
+ employeeRepositoryPagingAndSort.saveAll(Arrays.asList(john, bob, frank, jimmy));
+
+ Pageable pageable = PageRequest.of(0, 2, Sort.by("firstName"));
+
+ Page employees = employeeRepositoryPagingAndSort.findAll(pageable);
+
+ assertEquals(Arrays.asList(bob, frank), employees.get()
+ .collect(Collectors.toList()));
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/TestUtils.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/TestUtils.java
new file mode 100644
index 0000000000..989a7db247
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/TestUtils.java
@@ -0,0 +1,15 @@
+package com.baeldung.spring.data.persistence.springdatajpadifference;
+
+import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee;
+
+public class TestUtils {
+
+ public static Employee employee(String firstName, String lastname) {
+ Employee employee = new Employee();
+ employee.setFirstName(firstName);
+ employee.setLastName(lastname);
+ employee.setEmail(firstName + lastname + "@baeldung.com");
+
+ return employee;
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/resources/META-INF/persistence.xml b/persistence-modules/spring-data-jpa-repo-2/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 0000000000..94df50cf0a
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-repo-2/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,21 @@
+
+
+
+ org.hibernate.jpa.HibernatePersistenceProvider
+ com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index a5bdd2cf4c..3d2863e1f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1168,6 +1168,7 @@
spring-boot-modules/spring-boot-camel
spring-boot-modules/spring-boot-3
spring-boot-modules/spring-boot-3-native
+ spring-boot-modules/spring-boot-3-observation
spring-swagger-codegen/custom-validations-opeanpi-codegen
testing-modules/testing-assertions
persistence-modules/fauna
@@ -1251,6 +1252,7 @@
spring-boot-modules/spring-boot-camel
spring-boot-modules/spring-boot-3
spring-boot-modules/spring-boot-3-native
+ spring-boot-modules/spring-boot-3-observation
spring-swagger-codegen/custom-validations-opeanpi-codegen
testing-modules/testing-assertions
persistence-modules/fauna
diff --git a/spring-boot-modules/spring-boot-3-observation/pom.xml b/spring-boot-modules/spring-boot-3-observation/pom.xml
new file mode 100644
index 0000000000..ed613ee98e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+ spring-boot-3-observation
+ 0.0.1-SNAPSHOT
+ spring-boot-3-observation
+ Demo project for Spring Boot 3 Observation
+
+
+ com.baeldung
+ parent-boot-3
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-3
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.micrometer
+ micrometer-tracing
+
+
+ io.micrometer
+ micrometer-tracing-bridge-brave
+
+
+
+ io.micrometer
+ micrometer-observation-test
+ test
+
+
+ io.micrometer
+ micrometer-tracing-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+
+
+
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/GreetingApplication.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/GreetingApplication.java
new file mode 100644
index 0000000000..f5014a8abd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/GreetingApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.samples;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GreetingApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GreetingApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/SimpleObservationApplication.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/SimpleObservationApplication.java
new file mode 100644
index 0000000000..4434535939
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/SimpleObservationApplication.java
@@ -0,0 +1,66 @@
+package com.baeldung.samples;
+
+import io.micrometer.core.instrument.Measurement;
+import io.micrometer.core.instrument.Statistic;
+import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.observation.ObservationTextPublisher;
+
+import java.util.Optional;
+import java.util.stream.StreamSupport;
+
+public class SimpleObservationApplication {
+
+ // we can run this as a simple command line application
+ public static void main(String[] args) {
+ // create registry
+ final var observationRegistry = ObservationRegistry.create();
+ // create meter registry and observation handler
+ final var meterRegistry = new SimpleMeterRegistry();
+ final var meterObservationHandler = new DefaultMeterObservationHandler(meterRegistry);
+ // create simple logging observation handler
+ final var loggingObservationHandler = new ObservationTextPublisher(System.out::println);
+ // register observation handlers
+ observationRegistry
+ .observationConfig()
+ .observationHandler(meterObservationHandler)
+ .observationHandler(loggingObservationHandler);
+ // make an observation
+ Observation.Context context = new Observation.Context();
+ String observationName = "obs1";
+ Observation observation = Observation
+ .createNotStarted(observationName, () -> context, observationRegistry)
+ .lowCardinalityKeyValue("gender", "male")
+ .highCardinalityKeyValue("age", "41");
+
+ for (int i = 0; i < 10; i++) {
+ observation.observe(SimpleObservationApplication::doSomeAction);
+ }
+
+ meterRegistry.getMeters().forEach(m -> {
+ System.out.println(m.getId() + "\n============");
+ m.measure().forEach(ms -> System.out.println(ms.getValue() + " [" + ms.getStatistic() + "]"));
+ System.out.println("----------------------------");
+ });
+ Optional maximumDuration = meterRegistry.getMeters().stream()
+ .filter(m -> "obs1".equals(m.getId().getName()))
+ .flatMap(m -> StreamSupport.stream(m.measure().spliterator(), false))
+ .filter(ms -> ms.getStatistic() == Statistic.MAX)
+ .findFirst()
+ .map(Measurement::getValue);
+
+ System.out.println(maximumDuration);
+ }
+
+ private static void doSomeAction() {
+ try {
+ Thread.sleep(Math.round(Math.random() * 1000));
+ System.out.println("Hello World!");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/GreetingController.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/GreetingController.java
new file mode 100644
index 0000000000..bc179540f8
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/GreetingController.java
@@ -0,0 +1,26 @@
+package com.baeldung.samples.boundary;
+
+import com.baeldung.samples.domain.GreetingService;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping("/greet")
+public class GreetingController {
+
+ private final GreetingService service;
+
+ public GreetingController(GreetingService service) {
+ this.service = service;
+ }
+
+ @GetMapping(produces = MediaType.TEXT_PLAIN_VALUE)
+ @ResponseBody
+ public String sayHello() {
+ return this.service.sayHello();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/ObservationFilterConfiguration.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/ObservationFilterConfiguration.java
new file mode 100644
index 0000000000..c39af961a1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/ObservationFilterConfiguration.java
@@ -0,0 +1,22 @@
+package com.baeldung.samples.boundary;
+
+import io.micrometer.observation.ObservationRegistry;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.filter.ServerHttpObservationFilter;
+
+@Configuration
+public class ObservationFilterConfiguration {
+
+ // if an ObservationRegistry is already configured
+ @ConditionalOnBean(ObservationRegistry.class)
+ // if we do not use Actuator
+ @ConditionalOnMissingBean(ServerHttpObservationFilter.class)
+ @Bean
+ public ServerHttpObservationFilter observationFilter(ObservationRegistry registry) {
+ return new ServerHttpObservationFilter(registry);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationHandlerLogger.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationHandlerLogger.java
new file mode 100644
index 0000000000..0a1f52f9c1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationHandlerLogger.java
@@ -0,0 +1,28 @@
+package com.baeldung.samples.config;
+
+import io.micrometer.observation.ObservationHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ObservationHandlerLogger {
+
+ private static final Logger log = LoggerFactory.getLogger(ObservationHandlerLogger.class);
+
+ private static String toString(ObservationHandler> handler) {
+ return handler.getClass().getName() + " [ " + handler + "]";
+ }
+
+ @EventListener(ContextRefreshedEvent.class)
+ public void logObservationHandlers(ContextRefreshedEvent evt) {
+ evt.getApplicationContext().getBeansOfType(ObservationHandler.class)
+ .values()
+ .stream()
+ .map(ObservationHandlerLogger::toString)
+ .forEach(log::info);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationTextPublisherConfiguration.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationTextPublisherConfiguration.java
new file mode 100644
index 0000000000..29637166c9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationTextPublisherConfiguration.java
@@ -0,0 +1,21 @@
+package com.baeldung.samples.config;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationHandler;
+import io.micrometer.observation.ObservationTextPublisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ObservationTextPublisherConfiguration {
+
+ private static final Logger log = LoggerFactory.getLogger(ObservationTextPublisherConfiguration.class);
+
+ @Bean
+ public ObservationHandler observationTextPublisher() {
+ return new ObservationTextPublisher(log::info);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservedAspectConfiguration.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservedAspectConfiguration.java
new file mode 100644
index 0000000000..cd475113c7
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservedAspectConfiguration.java
@@ -0,0 +1,20 @@
+package com.baeldung.samples.config;
+
+import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.observation.aop.ObservedAspect;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+
+@AutoConfiguration
+@ConditionalOnClass(ObservedAspect.class)
+public class ObservedAspectConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ObservedAspect observedAspect(ObservationRegistry observationRegistry) {
+ return new ObservedAspect(observationRegistry);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/SimpleLoggingHandler.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/SimpleLoggingHandler.java
new file mode 100644
index 0000000000..c87aa68085
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/SimpleLoggingHandler.java
@@ -0,0 +1,59 @@
+package com.baeldung.samples.config;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SimpleLoggingHandler implements ObservationHandler {
+
+ private static final Logger log = LoggerFactory.getLogger(SimpleLoggingHandler.class);
+
+ private static String toString(Observation.Context context) {
+ return null == context ? "(no context)" : context.getName()
+ + " (" + context.getClass().getName() + "@" + System.identityHashCode(context) + ")";
+ }
+
+ private static String toString(Observation.Event event) {
+ return null == event ? "(no event)" : event.getName();
+ }
+
+ @Override
+ public boolean supportsContext(Observation.Context context) {
+ return true;
+ }
+
+ @Override
+ public void onStart(Observation.Context context) {
+ log.info("Starting context " + toString(context));
+ }
+
+ @Override
+ public void onError(Observation.Context context) {
+ log.info("Error for context " + toString(context));
+ }
+
+ @Override
+ public void onEvent(Observation.Event event, Observation.Context context) {
+ log.info("Event for context " + toString(context) + " [" + toString(event) + "]");
+ }
+
+ @Override
+ public void onScopeOpened(Observation.Context context) {
+ log.info("Scope opened for context " + toString(context));
+
+ }
+
+ @Override
+ public void onScopeClosed(Observation.Context context) {
+ log.info("Scope closed for context " + toString(context));
+ }
+
+ @Override
+ public void onStop(Observation.Context context) {
+ log.info("Stopping context " + toString(context));
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/domain/GreetingService.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/domain/GreetingService.java
new file mode 100644
index 0000000000..ec362dd3cc
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/domain/GreetingService.java
@@ -0,0 +1,14 @@
+package com.baeldung.samples.domain;
+
+import io.micrometer.observation.annotation.Observed;
+import org.springframework.stereotype.Service;
+
+@Observed(name = "greetingService")
+@Service
+public class GreetingService {
+
+ public String sayHello() {
+ return "Hello World!";
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/resources/application.yml b/spring-boot-modules/spring-boot-3-observation/src/main/resources/application.yml
new file mode 100644
index 0000000000..9f91e8a03a
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ #health,info,beans,metrics,startup
diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/config/SimpleLoggingHandlerUnitTest.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/config/SimpleLoggingHandlerUnitTest.java
new file mode 100644
index 0000000000..5a6d1bd23f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/config/SimpleLoggingHandlerUnitTest.java
@@ -0,0 +1,17 @@
+package com.baeldung.samples.config;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationHandler;
+import io.micrometer.observation.tck.AnyContextObservationHandlerCompatibilityKit;
+
+class SimpleLoggingHandlerUnitTest
+ extends AnyContextObservationHandlerCompatibilityKit {
+
+ SimpleLoggingHandler handler = new SimpleLoggingHandler();
+
+ @Override
+ public ObservationHandler handler() {
+ return handler;
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/EnableTestObservation.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/EnableTestObservation.java
new file mode 100644
index 0000000000..8e4e2a1da0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/EnableTestObservation.java
@@ -0,0 +1,44 @@
+package com.baeldung.samples.domain;
+
+import com.baeldung.samples.config.ObservedAspectConfiguration;
+import io.micrometer.observation.tck.TestObservationRegistry;
+import io.micrometer.tracing.test.simple.SimpleTracer;
+import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@AutoConfigureObservability
+@Import({
+ ObservedAspectConfiguration.class,
+ EnableTestObservation.ObservationTestConfiguration.class
+})
+public @interface EnableTestObservation {
+
+ @TestConfiguration
+ class ObservationTestConfiguration {
+
+ @Bean
+ TestObservationRegistry observationRegistry() {
+ return TestObservationRegistry.create();
+ }
+
+ @Bean
+ SimpleTracer simpleTracer() {
+ return new SimpleTracer();
+ }
+
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceObservationIntegrationTest.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceObservationIntegrationTest.java
new file mode 100644
index 0000000000..98fa175660
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceObservationIntegrationTest.java
@@ -0,0 +1,35 @@
+package com.baeldung.samples.domain;
+
+import io.micrometer.observation.tck.TestObservationRegistry;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static io.micrometer.observation.tck.TestObservationRegistryAssert.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@ComponentScan(basePackageClasses = GreetingService.class)
+@EnableAutoConfiguration
+@EnableTestObservation
+class GreetingServiceObservationIntegrationTest {
+
+ @Autowired
+ GreetingService service;
+ @Autowired
+ TestObservationRegistry registry;
+
+ @Test
+ void testObservation() {
+ // invoke service
+ service.sayHello();
+ assertThat(registry)
+ .hasObservationWithNameEqualTo("greetingService")
+ .that()
+ .hasBeenStarted()
+ .hasBeenStopped();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceTracingIntegrationTest.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceTracingIntegrationTest.java
new file mode 100644
index 0000000000..0199c0e7ef
--- /dev/null
+++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceTracingIntegrationTest.java
@@ -0,0 +1,42 @@
+package com.baeldung.samples.domain;
+
+import io.micrometer.tracing.test.simple.SimpleTracer;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static io.micrometer.tracing.test.simple.TracerAssert.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@ComponentScan(basePackageClasses = GreetingService.class)
+@EnableAutoConfiguration
+@EnableTestObservation
+class GreetingServiceTracingIntegrationTest {
+
+ @Autowired
+ GreetingService service;
+ @Value("${management.tracing.enabled:true}")
+ boolean tracingEnabled;
+ @Autowired
+ SimpleTracer tracer;
+
+ @Test
+ void testEnabledTracing() {
+ Assertions.assertThat(tracingEnabled).isTrue();
+ }
+
+ @Test
+ void testTracingForGreeting() {
+ service.sayHello();
+ assertThat(tracer)
+ .onlySpan()
+ .hasNameEqualTo("greeting-service#say-hello")
+ .isEnded();
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-graphql/pom.xml b/spring-boot-modules/spring-boot-graphql/pom.xml
index b4b449166f..bb475679ad 100644
--- a/spring-boot-modules/spring-boot-graphql/pom.xml
+++ b/spring-boot-modules/spring-boot-graphql/pom.xml
@@ -1,7 +1,7 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
spring-boot-graphql
spring-boot-graphql
@@ -13,6 +13,47 @@
1.0.0-SNAPSHOT
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.7.0
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-plugin.version}
+
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+
+
+ 3.19.2
+ 0.6.1
+ 1.43.2
+ 2.13.1.RELEASE
+ 1.5.1
+ 1.3.5
+ 1.6.2
+ 3.3.2
+
+
org.springframework.boot
@@ -78,45 +119,4 @@
-
-
-
- kr.motd.maven
- os-maven-plugin
- 1.7.0
-
-
-
-
- org.xolstice.maven.plugins
- protobuf-maven-plugin
- ${protobuf-plugin.version}
-
- com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
- grpc-java
- io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
-
-
-
-
- compile
- compile-custom
-
-
-
-
-
-
-
-
- 3.19.2
- 0.6.1
- 1.43.2
- 2.13.1.RELEASE
- 1.5.1
- 1.3.5
- 1.6.2
- 3.3.2
-
-
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml b/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml
index 889842df9f..0036bb3284 100644
--- a/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml
+++ b/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml
@@ -2,8 +2,10 @@ server:
port: 8082
spring:
+ main:
+ allow-bean-definition-overriding: true
graphql:
graphiql:
enabled: true
schema:
- locations: classpath:chooseapi/
\ No newline at end of file
+ locations: classpath:chooseapi/
diff --git a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java
index c1ce711388..48ed73fbde 100644
--- a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java
+++ b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java
@@ -11,7 +11,9 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ChooseApiApp.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = { "grpc.server.port=-1" }, // Disable gRPC external server
+ classes = ChooseApiApp.class)
@ActiveProfiles("chooseapi")
class BooksControllerGraphQLIntegrationTest {
diff --git a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java
index 977a132653..4f2f8e8e51 100644
--- a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java
+++ b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java
@@ -9,13 +9,15 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-@SpringBootTest
+@SpringBootTest(properties = { "grpc.server.port=-1" }) // Disable gRPC external server
+@ActiveProfiles("chooseapi")
@AutoConfigureMockMvc
class BooksControllerRestIntegrationTest {
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml
index c7d3f5d12c..397f06399f 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml
@@ -1,44 +1,54 @@
- 4.0.0
- spring-cloud-stream-kinesis
- spring-cloud-stream-kinesis
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+ spring-cloud-stream-kinesis
+ spring-cloud-stream-kinesis
-
- com.baeldung
- spring-cloud-stream
- 1.0.0-SNAPSHOT
-
+
+ com.baeldung
+ spring-cloud-stream
+ 1.0.0-SNAPSHOT
+
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.cloud
- spring-cloud-stream-binder-kinesis
- ${spring-cloud-stream-kinesis-binder.version}
-
-
- com.amazonaws
- aws-java-sdk-kinesis
- ${aws-sdk.version}
-
-
- org.springframework.cloud
- spring-cloud-stream-test-support
- ${spring-cloud-stream-test.version}
- test
-
-
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.amazonaws
+ aws-java-sdk-kinesis
+ ${aws-sdk.version}
+
+
+ org.springframework.cloud
+ spring-cloud-stream-test-support
+ ${spring-cloud-stream-test.version}
+ test
+
+
+ com.amazonaws
+ amazon-kinesis-producer
+ 0.13.1
+
+
+ com.amazonaws
+ amazon-kinesis-client
+ 1.11.2
+
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-kinesis
+ ${spring-cloud-stream-kinesis-binder.version}
+
+
-
- 1.11.632
- 2.0.2.RELEASE
- 2.2.1.RELEASE
-
+
+ 1.11.632
+ 2.0.2.RELEASE
+ 2.2.1.RELEASE
+
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/KinesisApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/KinesisApplication.java
deleted file mode 100644
index 6926560244..0000000000
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/KinesisApplication.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.baeldung;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.stream.annotation.EnableBinding;
-import org.springframework.cloud.stream.annotation.StreamListener;
-import org.springframework.cloud.stream.messaging.Processor;
-import org.springframework.context.annotation.Bean;
-import org.springframework.messaging.support.MessageBuilder;
-
-import com.amazonaws.auth.AWSStaticCredentialsProvider;
-import com.amazonaws.auth.BasicAWSCredentials;
-import com.amazonaws.regions.Regions;
-import com.amazonaws.services.kinesis.AmazonKinesis;
-import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder;
-
-@SpringBootApplication
-@EnableBinding(Processor.class)
-public class KinesisApplication {
-
- @Value("${aws.access.key}")
- private String accessKey;
-
- @Value("${aws.secret.key}")
- private String secretKey;
-
- @Autowired
- private Processor processor;
-
- public static void main(String[] args) {
- SpringApplication.run(KinesisApplication.class, args);
- }
-
- @Bean
- public AmazonKinesis buildAmazonKinesis() {
- BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
- return AmazonKinesisClientBuilder.standard()
- .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
- .withRegion(Regions.EU_CENTRAL_1)
- .build();
- }
-
- @StreamListener(Processor.INPUT)
- public void consume(String val) {
- System.out.println(val);
- }
-
- public void produce(String val) {
- processor.output().send(MessageBuilder.withPayload(val).build());
- }
-}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ConsumerBinder.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ConsumerBinder.java
new file mode 100644
index 0000000000..38ad634086
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ConsumerBinder.java
@@ -0,0 +1,16 @@
+package com.baeldung.binder;
+
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.annotation.StreamListener;
+import org.springframework.cloud.stream.messaging.Sink;
+import org.springframework.stereotype.Component;
+
+@Component
+@EnableBinding(Sink.class)
+public class ConsumerBinder {
+
+ @StreamListener(Sink.INPUT)
+ public void consume(String ip) {
+ System.out.println(ip);
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/KinesisBinderApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/KinesisBinderApplication.java
new file mode 100644
index 0000000000..e4f6916ed9
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/KinesisBinderApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.binder;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class KinesisBinderApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(KinesisBinderApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ProducerBinder.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ProducerBinder.java
new file mode 100644
index 0000000000..468f2886de
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ProducerBinder.java
@@ -0,0 +1,24 @@
+package com.baeldung.binder;
+
+import java.util.stream.IntStream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.messaging.Source;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+@EnableBinding(Source.class)
+public class ProducerBinder {
+
+ @Autowired
+ private Source source;
+
+ @Scheduled(fixedDelay = 3000L)
+ private void produce() {
+ IntStream.range(1, 200).mapToObj(ipSuffix -> "192.168.0." + ipSuffix)
+ .forEach(entry -> source.output().send(MessageBuilder.withPayload(entry).build()));
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessor.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessor.java
similarity index 96%
rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessor.java
rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessor.java
index 32e6babc86..c028f530dc 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessor.java
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessor.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.kclkpl;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor;
import com.amazonaws.services.kinesis.clientlibrary.types.InitializationInput;
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessorFactory.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessorFactory.java
similarity index 92%
rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessorFactory.java
rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessorFactory.java
index 1ca774bb39..7515e65eff 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessorFactory.java
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessorFactory.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.kclkpl;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessorFactory;
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProducer.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProducer.java
new file mode 100644
index 0000000000..76111cfe57
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProducer.java
@@ -0,0 +1,30 @@
+package com.baeldung.kclkpl;
+
+import java.nio.ByteBuffer;
+import java.util.stream.IntStream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import com.amazonaws.services.kinesis.producer.KinesisProducer;
+
+@Component
+public class IpProducer {
+
+ @Value("${ips.stream}")
+ private String IPS_STREAM;
+
+ @Value("${ips.partition.key}")
+ private String IPS_PARTITION_KEY;
+
+ @Autowired
+ private KinesisProducer kinesisProducer;
+
+ @Scheduled(fixedDelay = 3000L)
+ private void produce() {
+ IntStream.range(1, 200).mapToObj(ipSuffix -> ByteBuffer.wrap(("192.168.0." + ipSuffix).getBytes()))
+ .forEach(entry -> kinesisProducer.addUserRecord(IPS_STREAM, IPS_PARTITION_KEY, entry));
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKCLApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKCLApplication.java
new file mode 100644
index 0000000000..01c5af596d
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKCLApplication.java
@@ -0,0 +1,48 @@
+package com.baeldung.kclkpl;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.kinesis.clientlibrary.lib.worker.KinesisClientLibConfiguration;
+import com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker;
+
+@SpringBootApplication
+public class KinesisKCLApplication implements ApplicationRunner {
+
+ @Value("${aws.access.key}")
+ private String accessKey;
+
+ @Value("${aws.secret.key}")
+ private String secretKey;
+
+ @Value("${ips.stream}")
+ private String IPS_STREAM;
+
+ public static void main(String[] args) {
+ SpringApplication.run(KinesisKCLApplication.class, args);
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
+ KinesisClientLibConfiguration consumerConfig = new KinesisClientLibConfiguration(
+ "KinesisKCLConsumer",
+ IPS_STREAM,
+ new AWSStaticCredentialsProvider(awsCredentials),
+ "KinesisKCLConsumer")
+ .withRegionName(Regions.EU_CENTRAL_1.getName());
+
+ new Worker.Builder()
+ .recordProcessorFactory(new IpProcessorFactory())
+ .config(consumerConfig)
+ .build()
+ .run();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKPLApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKPLApplication.java
new file mode 100644
index 0000000000..4ff7cf8087
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKPLApplication.java
@@ -0,0 +1,38 @@
+package com.baeldung.kclkpl;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.kinesis.producer.KinesisProducer;
+import com.amazonaws.services.kinesis.producer.KinesisProducerConfiguration;
+
+@SpringBootApplication
+public class KinesisKPLApplication {
+
+ @Value("${aws.access.key}")
+ private String accessKey;
+
+ @Value("${aws.secret.key}")
+ private String secretKey;
+
+ public static void main(String[] args) {
+ SpringApplication.run(KinesisKPLApplication.class, args);
+ }
+
+ @Bean
+ public KinesisProducer kinesisProducer() {
+ BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
+ KinesisProducerConfiguration producerConfig = new KinesisProducerConfiguration()
+ .setCredentialsProvider(new AWSStaticCredentialsProvider(awsCredentials))
+ .setVerifyCertificate(false)
+ .setRegion(Regions.EU_CENTRAL_1.getName());
+
+ return new KinesisProducer(producerConfig);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpConsumer.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ConsumerSDK.java
similarity index 82%
rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpConsumer.java
rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ConsumerSDK.java
index 949787b687..d95d66b75a 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpConsumer.java
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ConsumerSDK.java
@@ -1,12 +1,9 @@
-package com.baeldung;
+package com.baeldung.sdk;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.cloud.stream.annotation.EnableBinding;
-import org.springframework.cloud.stream.annotation.StreamListener;
-import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.stereotype.Component;
import com.amazonaws.services.kinesis.AmazonKinesis;
@@ -17,8 +14,7 @@ import com.amazonaws.services.kinesis.model.GetShardIteratorResult;
import com.amazonaws.services.kinesis.model.ShardIteratorType;
@Component
-@EnableBinding(Sink.class)
-public class IpConsumer {
+public class ConsumerSDK {
@Value("${ips.stream}")
private String IPS_STREAM;
@@ -31,12 +27,7 @@ public class IpConsumer {
private GetShardIteratorResult shardIterator;
- @StreamListener(Sink.INPUT)
- public void consume(String ip) {
- System.out.println(ip);
- }
-
- private void consumeWithKinesis() {
+ public void consumeWithKinesis() {
GetRecordsRequest recordsRequest = new GetRecordsRequest();
recordsRequest.setShardIterator(shardIterator.getShardIterator());
recordsRequest.setLimit(25);
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/KinesisSDKApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/KinesisSDKApplication.java
new file mode 100644
index 0000000000..28901c0723
--- /dev/null
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/KinesisSDKApplication.java
@@ -0,0 +1,35 @@
+package com.baeldung.sdk;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.kinesis.AmazonKinesis;
+import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder;
+
+@SpringBootApplication
+public class KinesisSDKApplication {
+
+ @Value("${aws.access.key}")
+ private String accessKey;
+
+ @Value("${aws.secret.key}")
+ private String secretKey;
+
+ public static void main(String[] args) {
+ SpringApplication.run(KinesisSDKApplication.class, args);
+ }
+
+ @Bean
+ public AmazonKinesis buildAmazonKinesis() {
+ BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
+ return AmazonKinesisClientBuilder.standard()
+ .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
+ .withRegion(Regions.EU_CENTRAL_1)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProducer.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ProducerSDK.java
similarity index 72%
rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProducer.java
rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ProducerSDK.java
index f59b2161f9..76ece8ddb7 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProducer.java
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ProducerSDK.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.sdk;
import java.nio.ByteBuffer;
import java.util.List;
@@ -7,9 +7,6 @@ import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.cloud.stream.annotation.EnableBinding;
-import org.springframework.cloud.stream.messaging.Source;
-import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -18,8 +15,7 @@ import com.amazonaws.services.kinesis.model.PutRecordsRequest;
import com.amazonaws.services.kinesis.model.PutRecordsRequestEntry;
@Component
-@EnableBinding(Source.class)
-public class IpProducer {
+public class ProducerSDK {
@Value("${ips.partition.key}")
private String IPS_PARTITION_KEY;
@@ -27,17 +23,9 @@ public class IpProducer {
@Value("${ips.stream}")
private String IPS_STREAM;
- @Autowired
- private Source source;
@Autowired
private AmazonKinesis kinesis;
- @Scheduled(fixedDelay = 3000L)
- private void produce() {
- IntStream.range(1, 200).mapToObj(ipSuffix -> "192.168.0." + ipSuffix)
- .forEach(entry -> source.output().send(MessageBuilder.withPayload(entry).build()));
- }
-
@Scheduled(fixedDelay = 3000L)
private void produceWithKinesis() {
List entries = IntStream.range(1, 200).mapToObj(ipSuffix -> {
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties
index 1943766c26..777abef1cc 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties
@@ -1,6 +1,12 @@
+# configurations for AWS SDK consumer and producer
aws.access.key=my-aws-access-key-goes-here
aws.secret.key=my-aws-secret-key-goes-here
+ips.partition.key=ips-partition-key
+ips.stream=ips-stream
+ips.shard.id=1
+
+# configurations for Spring Cloud Stream Kineses Binder consumer and producer
cloud.aws.credentials.access-key=my-aws-access-key
cloud.aws.credentials.secret-key=my-aws-secret-key
cloud.aws.region.static=eu-central-1
@@ -11,8 +17,4 @@ spring.cloud.stream.bindings.input.group=live-ips-group
spring.cloud.stream.bindings.input.content-type=text/plain
spring.cloud.stream.bindings.output.destination=myStream
-spring.cloud.stream.bindings.output.content-type=text/plain
-
-ips.partition.key=ips-partition-key
-ips.stream=ips-stream
-ips.shard.id=1
\ No newline at end of file
+spring.cloud.stream.bindings.output.content-type=text/plain
\ No newline at end of file
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java
index a232d29be5..bbe871ea11 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java
@@ -5,11 +5,13 @@ import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
+import com.baeldung.kclkpl.KinesisKPLApplication;
+
/**
* Manual Test - this test needs correct AWS Access Key and Secret to build the Amazon Kinesis and complete successfully
*/
@RunWith(SpringRunner.class)
-@SpringBootTest(classes = KinesisApplication.class)
+@SpringBootTest(classes = KinesisKPLApplication.class)
public class KinesisApplicationManualTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties
index 1943766c26..777abef1cc 100644
--- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties
+++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties
@@ -1,6 +1,12 @@
+# configurations for AWS SDK consumer and producer
aws.access.key=my-aws-access-key-goes-here
aws.secret.key=my-aws-secret-key-goes-here
+ips.partition.key=ips-partition-key
+ips.stream=ips-stream
+ips.shard.id=1
+
+# configurations for Spring Cloud Stream Kineses Binder consumer and producer
cloud.aws.credentials.access-key=my-aws-access-key
cloud.aws.credentials.secret-key=my-aws-secret-key
cloud.aws.region.static=eu-central-1
@@ -11,8 +17,4 @@ spring.cloud.stream.bindings.input.group=live-ips-group
spring.cloud.stream.bindings.input.content-type=text/plain
spring.cloud.stream.bindings.output.destination=myStream
-spring.cloud.stream.bindings.output.content-type=text/plain
-
-ips.partition.key=ips-partition-key
-ips.stream=ips-stream
-ips.shard.id=1
\ No newline at end of file
+spring.cloud.stream.bindings.output.content-type=text/plain
\ No newline at end of file
diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java
index e8aa63a88d..463d3209ea 100644
--- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java
+++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java
@@ -1,5 +1,6 @@
package com.baeldung.spring.kafka;
+import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
@@ -8,15 +9,20 @@ import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.listener.ContainerProperties;
+import org.springframework.kafka.listener.DefaultErrorHandler;
import org.springframework.kafka.support.converter.RecordMessageConverter;
import org.springframework.kafka.support.converter.StringJsonMessageConverter;
import org.springframework.kafka.support.mapping.DefaultJackson2JavaTypeMapper;
import org.springframework.kafka.support.mapping.Jackson2JavaTypeMapper;
import org.springframework.kafka.support.serializer.JsonDeserializer;
+import org.springframework.util.backoff.BackOff;
+import org.springframework.util.backoff.FixedBackOff;
@EnableKafka
@Configuration
@@ -25,6 +31,12 @@ public class KafkaConsumerConfig {
@Value(value = "${spring.kafka.bootstrap-servers}")
private String bootstrapAddress;
+ @Value(value = "${kafka.backoff.interval}")
+ private Long interval;
+
+ @Value(value = "${kafka.backoff.max_failure}")
+ private Long maxAttempts;
+
public ConsumerFactory consumerFactory(String groupId) {
Map props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
@@ -71,7 +83,7 @@ public class KafkaConsumerConfig {
public ConcurrentKafkaListenerContainerFactory filterKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory = kafkaListenerContainerFactory("filter");
factory.setRecordFilterStrategy(record -> record.value()
- .contains("World"));
+ .contains("World"));
return factory;
}
@@ -83,7 +95,7 @@ public class KafkaConsumerConfig {
}
@Bean
- public ConcurrentKafkaListenerContainerFactory greetingKafkaListenerContainerFactory() {
+ public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(greetingConsumerFactory());
return factory;
@@ -109,15 +121,32 @@ public class KafkaConsumerConfig {
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
+ props.put(ConsumerConfig.GROUP_ID_CONFIG, "group_id_test");
return new DefaultKafkaConsumerFactory<>(props);
}
@Bean
- public ConcurrentKafkaListenerContainerFactory multiTypeKafkaListenerContainerFactory() {
+ @Primary
+ public ConcurrentKafkaListenerContainerFactory greetingKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(multiTypeConsumerFactory());
factory.setMessageConverter(multiTypeConverter());
+ factory.setCommonErrorHandler(errorHandler());
+ factory.getContainerProperties()
+ .setAckMode(ContainerProperties.AckMode.RECORD);
return factory;
}
+ @Bean
+ public DefaultErrorHandler errorHandler() {
+ BackOff fixedBackOff = new FixedBackOff(interval, maxAttempts);
+ DefaultErrorHandler errorHandler = new DefaultErrorHandler((consumerRecord, e) -> {
+ System.out.println(String.format("consumed record %s because this exception was thrown",consumerRecord.toString(),e.getClass().getName()));
+ }, fixedBackOff);
+ //Commented because of the test
+ //errorHandler.addRetryableExceptions(SocketTimeoutException.class,RuntimeException.class);
+ errorHandler.addNotRetryableExceptions(NullPointerException.class);
+ return errorHandler;
+ }
+
}
diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java
index 9afb5ff0b6..4b43c84f15 100644
--- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java
+++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java
@@ -2,6 +2,7 @@ package com.baeldung.spring.kafka;
import org.springframework.kafka.annotation.KafkaHandler;
import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
@Component
@@ -9,7 +10,12 @@ import org.springframework.stereotype.Component;
public class MultiTypeKafkaListener {
@KafkaHandler
+ //@RetryableTopic(backoff = @Backoff(value = 3000L), attempts = "5", autoCreateTopics = "false",include = SocketTimeoutException.class, exclude = NullPointerException.class)
public void handleGreeting(Greeting greeting) {
+ if (greeting.getName()
+ .equalsIgnoreCase("test")) {
+ throw new MessagingException("test not allowed");
+ }
System.out.println("Greeting received: " + greeting);
}
diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/RetryableApplicationKafkaApp.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/RetryableApplicationKafkaApp.java
new file mode 100644
index 0000000000..e43207829a
--- /dev/null
+++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/RetryableApplicationKafkaApp.java
@@ -0,0 +1,14 @@
+package com.baeldung.spring.kafka;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+
+@SpringBootApplication
+@Import(value = { KafkaTopicConfig.class, KafkaConsumerConfig.class, KafkaProducerConfig.class })
+public class RetryableApplicationKafkaApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RetryableApplicationKafkaApp.class, args);
+ }
+}
diff --git a/spring-kafka/src/main/resources/application.properties b/spring-kafka/src/main/resources/application.properties
index c57537e2af..691b6f55b7 100644
--- a/spring-kafka/src/main/resources/application.properties
+++ b/spring-kafka/src/main/resources/application.properties
@@ -14,4 +14,7 @@ monitor.producer.simulate=true
monitor.consumer.simulate=true
monitor.kafka.consumer.groupid.simulate=baeldungGrpSimulate
test.topic=testtopic1
+kafka.backoff.interval=9000
+kafka.backoff.max_failure=5
+
diff --git a/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java b/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java
index eebcf778be..030d166ca4 100644
--- a/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java
+++ b/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java
@@ -1,7 +1,6 @@
package com.baeldung.kafka.embedded;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -16,6 +15,8 @@ import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.test.annotation.DirtiesContext;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
@SpringBootTest
@DirtiesContext
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
@@ -33,6 +34,8 @@ class EmbeddedKafkaIntegrationTest {
@Value("${test.topic}")
private String topic;
+ private ObjectMapper objectMapper = new ObjectMapper();
+
@BeforeEach
void setup() {
consumer.resetLatch();
@@ -44,7 +47,8 @@ class EmbeddedKafkaIntegrationTest {
template.send(topic, data);
- boolean messageConsumed = consumer.getLatch().await(10, TimeUnit.SECONDS);
+ boolean messageConsumed = consumer.getLatch()
+ .await(10, TimeUnit.SECONDS);
assertTrue(messageConsumed);
assertThat(consumer.getPayload(), containsString(data));
}
@@ -55,7 +59,8 @@ class EmbeddedKafkaIntegrationTest {
producer.send(topic, data);
- boolean messageConsumed = consumer.getLatch().await(10, TimeUnit.SECONDS);
+ boolean messageConsumed = consumer.getLatch()
+ .await(10, TimeUnit.SECONDS);
assertTrue(messageConsumed);
assertThat(consumer.getPayload(), containsString(data));
}
diff --git a/spring-kafka/src/test/java/com/baeldung/spring/kafka/KafkaRetryableIntegrationTest.java b/spring-kafka/src/test/java/com/baeldung/spring/kafka/KafkaRetryableIntegrationTest.java
new file mode 100644
index 0000000000..029031923e
--- /dev/null
+++ b/spring-kafka/src/test/java/com/baeldung/spring/kafka/KafkaRetryableIntegrationTest.java
@@ -0,0 +1,84 @@
+package com.baeldung.spring.kafka;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.listener.AcknowledgingConsumerAwareMessageListener;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+import org.springframework.kafka.test.EmbeddedKafkaBroker;
+import org.springframework.kafka.test.context.EmbeddedKafka;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@SpringBootTest(classes = RetryableApplicationKafkaApp.class)
+@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
+public class KafkaRetryableIntegrationTest {
+ @ClassRule
+ public static EmbeddedKafkaBroker embeddedKafka = new EmbeddedKafkaBroker(1, true, "multitype");
+
+ @Autowired
+ private KafkaListenerEndpointRegistry registry;
+
+ @Autowired
+ private KafkaTemplate template;
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ private static final String CONTAINER_GROUP = "multiGroup";
+
+ private static final String TOPIC = "topic";
+
+ @Before
+ public void setup() {
+ System.setProperty("spring.kafka.bootstrap-servers", embeddedKafka.getBrokersAsString());
+ }
+
+ @Test
+ public void givenEmbeddedKafkaBroker_whenSendingAWellFormedMessage_thenMessageIsConsumed() throws Exception {
+ ConcurrentMessageListenerContainer, ?> container = (ConcurrentMessageListenerContainer, ?>) registry.getListenerContainer(CONTAINER_GROUP);
+ container.stop();
+ @SuppressWarnings("unchecked") AcknowledgingConsumerAwareMessageListener messageListener = (AcknowledgingConsumerAwareMessageListener) container.getContainerProperties()
+ .getMessageListener();
+ CountDownLatch latch = new CountDownLatch(1);
+ container.getContainerProperties()
+ .setMessageListener((AcknowledgingConsumerAwareMessageListener) (data, acknowledgment, consumer) -> {
+ messageListener.onMessage(data, acknowledgment, consumer);
+ latch.countDown();
+ });
+ Greeting greeting = new Greeting("test1", "test2");
+ container.start();
+ template.send(TOPIC, objectMapper.writeValueAsString(greeting));
+ assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
+ }
+
+ @Test
+ public void givenEmbeddedKafkaBroker_whenSendingAMalFormedMessage_thenMessageIsConsumedAfterRetry() throws Exception {
+ ConcurrentMessageListenerContainer, ?> container = (ConcurrentMessageListenerContainer, ?>) registry.getListenerContainer(CONTAINER_GROUP);
+ container.stop();
+ @SuppressWarnings("unchecked") AcknowledgingConsumerAwareMessageListener messageListener = (AcknowledgingConsumerAwareMessageListener) container.getContainerProperties()
+ .getMessageListener();
+ CountDownLatch latch = new CountDownLatch(1);
+ container.getContainerProperties()
+ .setMessageListener((AcknowledgingConsumerAwareMessageListener) (data, acknowledgment, consumer) -> {
+ messageListener.onMessage(data, acknowledgment, consumer);
+ latch.countDown();
+ });
+ container.start();
+ Greeting greeting = new Greeting("test", "test");
+ template.send(TOPIC, objectMapper.writeValueAsString(greeting));
+ //this message will go on error
+ Greeting greeting2 = new Greeting("test2", "test2");
+ template.send(TOPIC, objectMapper.writeValueAsString(greeting2));
+ assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
+ }
+
+}
diff --git a/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java b/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java
index bf278cea90..58b80ff07e 100644
--- a/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java
+++ b/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java
@@ -51,4 +51,12 @@ public class OrderAgnosticListComparisonUnitTest {
assertThat(a).hasSameElementsAs(b);
}
+
+ @Test
+ void whenTestingForOrderAgnosticEqualityWithDuplicateElementsBothList_ShouldBeEqual() {
+ List a = Arrays.asList("a", "a", "b", "c");
+ List b = Arrays.asList("a", "b", "a", "c");
+
+ assertThat(a).containsExactlyInAnyOrderElementsOf(b);
+ }
}