diff --git a/aws-lambda/.gitignore b/aws-lambda/.gitignore
new file mode 100644
index 0000000000..9984c2e554
--- /dev/null
+++ b/aws-lambda/.gitignore
@@ -0,0 +1 @@
+.aws-sam/
diff --git a/aws-lambda/README.md b/aws-lambda/README.md
index 759c9dd506..0ae188fc97 100644
--- a/aws-lambda/README.md
+++ b/aws-lambda/README.md
@@ -6,3 +6,4 @@ This module contains articles about AWS Lambda
- [Using AWS Lambda with API Gateway](https://www.baeldung.com/aws-lambda-api-gateway)
- [Introduction to AWS Serverless Application Model](https://www.baeldung.com/aws-serverless)
- [How to Implement Hibernate in an AWS Lambda Function in Java](https://www.baeldung.com/java-aws-lambda-hibernate)
+- [Writing an Enterprise-Grade AWS Lambda in Java](https://www.baeldung.com/java-enterprise-aws-lambda)
diff --git a/aws-lambda/pom.xml b/aws-lambda/pom.xml
index 5dc275141d..8014a87126 100644
--- a/aws-lambda/pom.xml
+++ b/aws-lambda/pom.xml
@@ -17,6 +17,7 @@
lambda
shipping-tracker/ShippingFunction
+ todo-reminder/ToDoFunction
diff --git a/aws-lambda/todo-reminder/ToDoFunction/pom.xml b/aws-lambda/todo-reminder/ToDoFunction/pom.xml
new file mode 100644
index 0000000000..f80cbdf22f
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/pom.xml
@@ -0,0 +1,105 @@
+
+ 4.0.0
+ helloworld
+ HelloWorld
+ 1.0
+ jar
+ To Do Application Example.
+
+ 1.8
+ 1.8
+
+
+
+
+ com.amazonaws
+ aws-lambda-java-core
+ 1.2.1
+
+
+ com.amazonaws
+ aws-lambda-java-events
+ 3.6.0
+
+
+ uk.org.webcompere
+ lightweight-config
+ 1.1.0
+
+
+ com.amazonaws
+ aws-lambda-java-log4j2
+ 1.2.0
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ 2.13.2
+
+
+ io.github.openfeign
+ feign-core
+ 11.2
+
+
+ io.github.openfeign
+ feign-slf4j
+ 11.2
+
+
+ io.github.openfeign
+ feign-gson
+ 11.2
+
+
+ com.google.inject
+ guice
+ 5.0.1
+
+
+ junit
+ junit
+ 4.13.1
+ test
+
+
+ uk.org.webcompere
+ system-stubs-junit4
+ 1.2.0
+ test
+
+
+ org.mockito
+ mockito-core
+ 3.3.0
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.19.0
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/App.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/App.java
new file mode 100644
index 0000000000..b322dabca8
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/App.java
@@ -0,0 +1,43 @@
+package com.baeldung.lambda.todo;
+
+import java.io.*;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+import com.baeldung.lambda.todo.config.ExecutionContext;
+import com.baeldung.lambda.todo.service.PostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler for requests to Lambda function.
+ */
+public class App implements RequestStreamHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
+
+ private String environmentName = System.getenv("ENV_NAME");
+ private ExecutionContext executionContext = new ExecutionContext();
+
+ @Override
+ public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
+ context.getLogger().log("App starting\n");
+ context.getLogger().log("Environment: "
+ + environmentName + "\n");
+
+ try {
+ PostService postService = executionContext.getPostService();
+ executionContext.getToDoReaderService()
+ .getOldestToDo()
+ .ifPresent(postService::makePost);
+ } catch (Exception e) {
+ LOGGER.error("Failed: {}", e.getMessage(), e);
+ }
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostApi.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostApi.java
new file mode 100644
index 0000000000..0521dfe05c
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostApi.java
@@ -0,0 +1,8 @@
+package com.baeldung.lambda.todo.api;
+
+import feign.RequestLine;
+
+public interface PostApi {
+ @RequestLine("POST /posts")
+ void makePost(PostItem item);
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostItem.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostItem.java
new file mode 100644
index 0000000000..96049e37d4
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostItem.java
@@ -0,0 +1,40 @@
+package com.baeldung.lambda.todo.api;
+
+public class PostItem {
+ private String title;
+ private String body;
+ private int userId;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public void setUserId(int userId) {
+ this.userId = userId;
+ }
+
+ @Override
+ public String toString() {
+ return "PostItem{"
+ + "title='" + title + '\''
+ + ", body='" + body + '\''
+ + ", userId=" + userId +
+ '}';
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoApi.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoApi.java
new file mode 100644
index 0000000000..bf75cd566d
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoApi.java
@@ -0,0 +1,10 @@
+package com.baeldung.lambda.todo.api;
+
+import feign.RequestLine;
+
+import java.util.List;
+
+public interface ToDoApi {
+ @RequestLine("GET /todos")
+ List getAllTodos();
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoItem.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoItem.java
new file mode 100644
index 0000000000..d43b7a947c
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoItem.java
@@ -0,0 +1,50 @@
+package com.baeldung.lambda.todo.api;
+
+public class ToDoItem {
+ private int userId;
+ private int id;
+ private String title;
+ private boolean completed;
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public void setUserId(int userId) {
+ this.userId = userId;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ }
+
+ @Override
+ public String toString() {
+ return "ToDoItem{"
+ + "userId=" + userId
+ + ", id=" + id
+ + ", title='" + title + '\''
+ + ", completed=" + completed +
+ '}';
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Config.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Config.java
new file mode 100644
index 0000000000..f919ac4006
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Config.java
@@ -0,0 +1,50 @@
+package com.baeldung.lambda.todo.config;
+
+public class Config {
+ private String toDoEndpoint;
+ private String postEndpoint;
+ private String environmentName;
+
+ private Credentials toDoCredentials;
+ private Credentials postCredentials;
+
+ public String getToDoEndpoint() {
+ return toDoEndpoint;
+ }
+
+ public void setToDoEndpoint(String toDoEndpoint) {
+ this.toDoEndpoint = toDoEndpoint;
+ }
+
+ public String getPostEndpoint() {
+ return postEndpoint;
+ }
+
+ public void setPostEndpoint(String postEndpoint) {
+ this.postEndpoint = postEndpoint;
+ }
+
+ public String getEnvironmentName() {
+ return environmentName;
+ }
+
+ public void setEnvironmentName(String environmentName) {
+ this.environmentName = environmentName;
+ }
+
+ public Credentials getToDoCredentials() {
+ return toDoCredentials;
+ }
+
+ public void setToDoCredentials(Credentials toDoCredentials) {
+ this.toDoCredentials = toDoCredentials;
+ }
+
+ public Credentials getPostCredentials() {
+ return postCredentials;
+ }
+
+ public void setPostCredentials(Credentials postCredentials) {
+ this.postCredentials = postCredentials;
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Credentials.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Credentials.java
new file mode 100644
index 0000000000..a11399381c
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Credentials.java
@@ -0,0 +1,22 @@
+package com.baeldung.lambda.todo.config;
+
+public class Credentials {
+ private String username;
+ private String password;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/ExecutionContext.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/ExecutionContext.java
new file mode 100644
index 0000000000..b588187abd
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/ExecutionContext.java
@@ -0,0 +1,36 @@
+package com.baeldung.lambda.todo.config;
+
+import com.baeldung.lambda.todo.service.PostService;
+import com.baeldung.lambda.todo.service.ToDoReaderService;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ExecutionContext {
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(ExecutionContext.class);
+
+ private ToDoReaderService toDoReaderService;
+ private PostService postService;
+
+ public ExecutionContext() {
+ LOGGER.info("Loading configuration");
+
+ try {
+ Injector injector = Guice.createInjector(new Services());
+ this.toDoReaderService = injector.getInstance(ToDoReaderService.class);
+ this.postService = injector.getInstance(PostService.class);
+ } catch (Exception e) {
+ LOGGER.error("Could not start", e);
+ }
+ }
+
+ public ToDoReaderService getToDoReaderService() {
+ return toDoReaderService;
+ }
+
+ public PostService getPostService() {
+ return postService;
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Services.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Services.java
new file mode 100644
index 0000000000..c1d85012eb
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Services.java
@@ -0,0 +1,38 @@
+package com.baeldung.lambda.todo.config;
+
+import com.baeldung.lambda.todo.api.PostApi;
+import com.baeldung.lambda.todo.api.ToDoApi;
+import com.google.inject.AbstractModule;
+import feign.Feign;
+import feign.auth.BasicAuthRequestInterceptor;
+import feign.gson.GsonDecoder;
+import feign.gson.GsonEncoder;
+import feign.slf4j.Slf4jLogger;
+import uk.org.webcompere.lightweightconfig.ConfigLoader;
+
+import static feign.Logger.Level.FULL;
+
+public class Services extends AbstractModule {
+ @Override
+ protected void configure() {
+ Config config = ConfigLoader.loadYmlConfigFromResource("configuration.yml", Config.class);
+
+ ToDoApi toDoApi = Feign.builder()
+ .decoder(new GsonDecoder())
+ .logger(new Slf4jLogger())
+ .logLevel(FULL)
+ .requestInterceptor(new BasicAuthRequestInterceptor(config.getToDoCredentials().getUsername(), config.getToDoCredentials().getPassword()))
+ .target(ToDoApi.class, config.getToDoEndpoint());
+
+ PostApi postApi = Feign.builder()
+ .encoder(new GsonEncoder())
+ .logger(new Slf4jLogger())
+ .logLevel(FULL)
+ .requestInterceptor(new BasicAuthRequestInterceptor(config.getPostCredentials().getUsername(), config.getPostCredentials().getPassword()))
+ .target(PostApi.class, config.getPostEndpoint());
+
+ bind(Config.class).toInstance(config);
+ bind(ToDoApi.class).toInstance(toDoApi);
+ bind(PostApi.class).toInstance(postApi);
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/PostService.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/PostService.java
new file mode 100644
index 0000000000..bb0a67dbd1
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/PostService.java
@@ -0,0 +1,30 @@
+package com.baeldung.lambda.todo.service;
+
+import com.baeldung.lambda.todo.api.PostApi;
+import com.baeldung.lambda.todo.api.PostItem;
+import com.baeldung.lambda.todo.api.ToDoItem;
+import com.google.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+public class PostService {
+ private static final Logger LOGGER = LogManager.getLogger(PostService.class);
+ private PostApi postApi;
+
+ @Inject
+ public PostService(PostApi postApi) {
+ this.postApi = postApi;
+ }
+
+ public void makePost(ToDoItem toDoItem) {
+ LOGGER.info("Posting about: {}", toDoItem);
+ PostItem item = new PostItem();
+ item.setTitle("To Do is Out Of Date: " + toDoItem.getId());
+ item.setUserId(toDoItem.getUserId());
+ item.setBody("Not done: " + toDoItem.getTitle());
+
+ LOGGER.info("Post: {}", item);
+ postApi.makePost(item);
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/ToDoReaderService.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/ToDoReaderService.java
new file mode 100644
index 0000000000..5cd18ff46a
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/ToDoReaderService.java
@@ -0,0 +1,30 @@
+package com.baeldung.lambda.todo.service;
+
+import com.baeldung.lambda.todo.api.ToDoApi;
+import com.baeldung.lambda.todo.api.ToDoItem;
+import com.baeldung.lambda.todo.config.Config;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.Optional;
+
+public class ToDoReaderService {
+ // Log4j logger
+ private static final Logger LOGGER = LogManager.getLogger(ToDoReaderService.class);
+
+ private ToDoApi toDoApi;
+
+ @Inject
+ public ToDoReaderService(Config configuration, ToDoApi toDoApi) {
+ LOGGER.info("ToDo Endpoint on: {}", configuration.getToDoEndpoint());
+
+ this.toDoApi = toDoApi;
+ }
+
+ public Optional getOldestToDo() {
+ return toDoApi.getAllTodos().stream()
+ .filter(item -> !item.isCompleted())
+ .findFirst();
+ }
+}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/configuration.yml b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/configuration.yml
new file mode 100644
index 0000000000..f67239bc23
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/configuration.yml
@@ -0,0 +1,9 @@
+toDoEndpoint: https://jsonplaceholder.typicode.com
+postEndpoint: https://jsonplaceholder.typicode.com
+environmentName: ${ENV_NAME}
+toDoCredentials:
+ username: baeldung
+ password: ${TODO_PASSWORD:-password}
+postCredentials:
+ username: baeldung
+ password: ${POST_PASSWORD:-password}
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/log4j2.xml b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..b241e5d167
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/log4j2.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1} - %m%n
+
+
+
+
+
+
+
+
+
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/AppTest.java b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/AppTest.java
new file mode 100644
index 0000000000..cbdc8c22cb
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/AppTest.java
@@ -0,0 +1,58 @@
+package com.baeldung.lambda.todo;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.baeldung.lambda.todo.config.Config;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import uk.org.webcompere.lightweightconfig.ConfigLoader;
+import uk.org.webcompere.systemstubs.rules.EnvironmentVariablesRule;
+import uk.org.webcompere.systemstubs.stream.input.LinesAltStream;
+import uk.org.webcompere.systemstubs.stream.output.NoopStream;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AppTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mockContext;
+
+ @Rule
+ public EnvironmentVariablesRule environmentVariablesRule = new EnvironmentVariablesRule();
+
+ private InputStream fakeInputStream = new LinesAltStream();
+ private OutputStream fakeOutputStream = new NoopStream();
+
+ @Test
+ public void whenTheEnvironmentVariableIsSet_thenItIsLogged() throws Exception {
+ environmentVariablesRule.set("ENV_NAME", "unitTest");
+ new App().handleRequest(fakeInputStream, fakeOutputStream, mockContext);
+
+ verify(mockContext.getLogger())
+ .log("Environment: unitTest\n");
+ }
+
+ @Test
+ public void givenEnvironmentVariableIsNotSet_thenUseDefault() {
+ String setting = Optional.ofNullable(System.getenv("SETTING"))
+ .orElse("default");
+
+ assertThat(setting).isEqualTo("default");
+ }
+
+ @Test
+ public void givenConfiguration_canLoadIntoPojo() {
+ environmentVariablesRule.set("ENV_NAME", "unitTest");
+ Config config = ConfigLoader.loadYmlConfigFromResource("configuration.yml", Config.class);
+ assertThat(config.getEnvironmentName()).isEqualTo("unitTest");
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/service/ToDoReaderServiceTest.java b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/service/ToDoReaderServiceTest.java
new file mode 100644
index 0000000000..634c5257ff
--- /dev/null
+++ b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/service/ToDoReaderServiceTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.lambda.todo.service;
+
+import com.baeldung.lambda.todo.config.Config;
+import org.junit.Rule;
+import org.junit.Test;
+import uk.org.webcompere.systemstubs.rules.SystemOutRule;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+public class ToDoReaderServiceTest {
+
+ @Rule
+ public SystemOutRule systemOutRule = new SystemOutRule();
+
+ @Test
+ public void whenTheServiceStarts_thenItOutputsEndpoint() {
+ Config config = new Config();
+ config.setToDoEndpoint("https://todo-endpoint.com");
+ ToDoReaderService service = new ToDoReaderService(config, null);
+
+ assertThat(systemOutRule.getLinesNormalized())
+ .contains("ToDo Endpoint on: https://todo-endpoint.com");
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda/todo-reminder/template.yaml b/aws-lambda/todo-reminder/template.yaml
new file mode 100644
index 0000000000..f32ee392c5
--- /dev/null
+++ b/aws-lambda/todo-reminder/template.yaml
@@ -0,0 +1,22 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: todo-reminder application
+
+Parameters:
+ EnvironmentName:
+ Type: String
+ Default: dev
+
+Resources:
+ ToDoFunction:
+ Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
+ Properties:
+ Timeout: 20
+ CodeUri: ToDoFunction
+ Handler: com.baeldung.lambda.todo.App::handleRequest
+ Runtime: java8
+ MemorySize: 512
+ Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
+ Variables:
+ PARAM1: VALUE
+ ENV_NAME: !Ref EnvironmentName
\ No newline at end of file
diff --git a/core-java-modules/core-java-11-2/README.md b/core-java-modules/core-java-11-2/README.md
index c87936b07d..ca9a306b82 100644
--- a/core-java-modules/core-java-11-2/README.md
+++ b/core-java-modules/core-java-11-2/README.md
@@ -7,3 +7,4 @@ This module contains articles about Java 11 core features
- [Guide to Java Reflection](http://www.baeldung.com/java-reflection)
- [Guide to Java 8’s Collectors](https://www.baeldung.com/java-8-collectors)
- [New Features in Java 11](https://www.baeldung.com/java-11-new-features)
+- [Getting the Java Version at Runtime](https://www.baeldung.com/get-java-version-runtime)
diff --git a/core-java-modules/core-java-9-improvements/src/test/java/com/baeldung/java9/io/conversion/InputStreamToByteArrayUnitTest.java b/core-java-modules/core-java-9-improvements/src/test/java/com/baeldung/java9/io/conversion/InputStreamToByteArrayUnitTest.java
index b64709be09..3450ac5627 100644
--- a/core-java-modules/core-java-9-improvements/src/test/java/com/baeldung/java9/io/conversion/InputStreamToByteArrayUnitTest.java
+++ b/core-java-modules/core-java-9-improvements/src/test/java/com/baeldung/java9/io/conversion/InputStreamToByteArrayUnitTest.java
@@ -15,24 +15,43 @@ public class InputStreamToByteArrayUnitTest {
@Test
public final void givenUsingPlainJavaOnFixedSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
- final InputStream initialStream = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
- final byte[] targetArray = new byte[initialStream.available()];
- initialStream.read(targetArray);
+ final InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
+ final byte[] targetArray = new byte[is.available()];
+
+ is.read(targetArray);
}
@Test
public final void givenUsingPlainJavaOnUnknownSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
- final InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
-
+ final InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5, 6 });
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
int nRead;
- final byte[] data = new byte[1024];
+ final byte[] data = new byte[4];
+
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
- final byte[] byteArray = buffer.toByteArray();
+ final byte[] targetArray = buffer.toByteArray();
+ }
+
+ @Test
+ public final void givenUsingPlainJava9OnUnknownSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
+ final InputStream is = new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5, 6 });
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ int nRead;
+ final byte[] data = new byte[4];
+
+ while ((nRead = is.readNBytes(data, 0, data.length)) != 0) {
+ System.out.println("here " + nRead);
+ buffer.write(data, 0, nRead);
+ }
+
+ buffer.flush();
+ final byte[] targetArray = buffer.toByteArray();
}
@Test
diff --git a/core-java-modules/core-java-9-jigsaw/pom.xml b/core-java-modules/core-java-9-jigsaw/pom.xml
index 0797003174..a26a88f4b0 100644
--- a/core-java-modules/core-java-9-jigsaw/pom.xml
+++ b/core-java-modules/core-java-9-jigsaw/pom.xml
@@ -34,4 +34,4 @@
1.9
-
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-9-streams/pom.xml b/core-java-modules/core-java-9-streams/pom.xml
index e59cc347c7..aeaf2c7f57 100644
--- a/core-java-modules/core-java-9-streams/pom.xml
+++ b/core-java-modules/core-java-9-streams/pom.xml
@@ -7,7 +7,7 @@
0.1.0-SNAPSHOT
core-java-9-streams
jar
-
+
com.baeldung.core-java-modules
core-java-modules
@@ -25,4 +25,4 @@
-
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-arrays-guides/README.md b/core-java-modules/core-java-arrays-guides/README.md
index 7338ff9523..00bb6b53c8 100644
--- a/core-java-modules/core-java-arrays-guides/README.md
+++ b/core-java-modules/core-java-arrays-guides/README.md
@@ -8,3 +8,4 @@ This module contains complete guides about arrays in Java
- [What is \[Ljava.lang.Object;?](https://www.baeldung.com/java-tostring-array)
- [Guide to ArrayStoreException](https://www.baeldung.com/java-arraystoreexception)
- [Creating a Generic Array in Java](https://www.baeldung.com/java-generic-array)
+- [Maximum Size of Java Arrays](https://www.baeldung.com/java-arrays-max-size)
diff --git a/core-java-modules/core-java-arrays-guides/src/main/java/com/baeldung/array/MaxSizeArray.java b/core-java-modules/core-java-arrays-guides/src/main/java/com/baeldung/array/MaxSizeArray.java
new file mode 100644
index 0000000000..76a6a9290a
--- /dev/null
+++ b/core-java-modules/core-java-arrays-guides/src/main/java/com/baeldung/array/MaxSizeArray.java
@@ -0,0 +1,15 @@
+package com.baeldung.array;
+
+public class MaxSizeArray {
+
+ public static void main(String... strings) {
+ for (int i = 2; i >= 0; i--) {
+ try {
+ int[] arr = new int[Integer.MAX_VALUE - i];
+ System.out.println("Max-Size : "+ arr.length);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/core-java-modules/core-java-arrays-guides/src/test/java/com/baeldung/arrays/MaxArrySizeUnitTest.java b/core-java-modules/core-java-arrays-guides/src/test/java/com/baeldung/arrays/MaxArrySizeUnitTest.java
new file mode 100644
index 0000000000..ab79e83247
--- /dev/null
+++ b/core-java-modules/core-java-arrays-guides/src/test/java/com/baeldung/arrays/MaxArrySizeUnitTest.java
@@ -0,0 +1,30 @@
+package com.baeldung.arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MaxArrySizeUnitTest {
+
+ @Test
+ public void whenInitialArrayMoreThanMaxSize_thenThrowArray() {
+ boolean initalized = false;
+ try {
+ int[] arr = new int[Integer.MAX_VALUE - 1];
+ initalized = true;
+ } catch (Throwable e) {
+ Assert.assertTrue(e.getMessage().contains("Requested array size exceeds VM limit"));
+ }
+ Assert.assertFalse(initalized);
+ }
+
+ @Test
+ public void whenInitialArrayLessThanMaxSize_thenThrowArray() {
+ int[] arr = null;
+ try {
+ arr = new int[Integer.MAX_VALUE - 2];
+ } catch (Throwable e) {
+ Assert.assertTrue(e.getMessage().contains("Java heap space"));
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-exceptions-3/README.md b/core-java-modules/core-java-exceptions-3/README.md
index e1372381a8..f79eb41a8b 100644
--- a/core-java-modules/core-java-exceptions-3/README.md
+++ b/core-java-modules/core-java-exceptions-3/README.md
@@ -8,3 +8,4 @@
- [Localizing Exception Messages in Java](https://www.baeldung.com/java-localize-exception-messages)
- [Explanation of ClassCastException in Java](https://www.baeldung.com/java-classcastexception)
- [NoSuchFieldError in Java](https://www.baeldung.com/java-nosuchfielderror)
+- [IllegalAccessError in Java](https://www.baeldung.com/java-illegalaccesserror)
diff --git a/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/Class1.java b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/Class1.java
new file mode 100644
index 0000000000..d50d2bc5f5
--- /dev/null
+++ b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/Class1.java
@@ -0,0 +1,8 @@
+package com.baeldung.exceptions.illegalaccesserror;
+
+public class Class1 {
+
+ public void bar() {
+ System.out.println("SUCCESS");
+ }
+}
diff --git a/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/Class2.java b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/Class2.java
new file mode 100644
index 0000000000..766ceccb6b
--- /dev/null
+++ b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/Class2.java
@@ -0,0 +1,10 @@
+package com.baeldung.exceptions.illegalaccesserror;
+
+public class Class2 {
+
+ public void foo() {
+ Class1 c1 = new Class1();
+ c1.bar();
+ }
+}
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorExample.java b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorExample.java
new file mode 100644
index 0000000000..2cc1abbb1d
--- /dev/null
+++ b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorExample.java
@@ -0,0 +1,20 @@
+package com.baeldung.exceptions.illegalaccesserror;
+
+public class IllegalAccessErrorExample {
+
+ interface Baeldung {
+ public default void foobar() {
+ System.out.println("This is a default method.");
+ }
+ }
+
+ class Super {
+ private void foobar() {
+ System.out.println("SuperClass method foobar");
+ }
+ }
+
+ class MySubClass extends Super implements Baeldung {
+
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorSolved.java b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorSolved.java
new file mode 100644
index 0000000000..8f6a4c14a4
--- /dev/null
+++ b/core-java-modules/core-java-exceptions-3/src/main/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorSolved.java
@@ -0,0 +1,20 @@
+package com.baeldung.exceptions.illegalaccesserror;
+
+public class IllegalAccessErrorSolved {
+
+ interface BaeldungSolved {
+ public default void foobar() {
+ System.out.println("This is a default method.");
+ }
+ }
+
+ class SuperSolved {
+ public void foobar() {
+ System.out.println("SuperClass method foobar");
+ }
+ }
+
+ class MySubClassSolved extends SuperSolved implements BaeldungSolved {
+
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-exceptions-3/src/test/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorExampleUnitTest.java b/core-java-modules/core-java-exceptions-3/src/test/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorExampleUnitTest.java
new file mode 100644
index 0000000000..201e782229
--- /dev/null
+++ b/core-java-modules/core-java-exceptions-3/src/test/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorExampleUnitTest.java
@@ -0,0 +1,21 @@
+package com.baeldung.exceptions.illegalaccesserror;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class IllegalAccessErrorExampleUnitTest {
+
+ @Test()
+ public void givenInterfaceDefaultMethOverriddenPrivateAccess_whenInvoked_thenIllegalAccessError() {
+ Assertions.assertThrows(IllegalAccessError.class, () -> {
+ new IllegalAccessErrorExample().new MySubClass().foobar();
+ });
+ }
+
+ @Test()
+ public void givenClass1Class2_whenSameClassDefintion_thenNoIllegalAccessError() {
+ Assertions.assertDoesNotThrow(() -> {
+ new Class2().foo();
+ });
+ }
+}
diff --git a/core-java-modules/core-java-exceptions-3/src/test/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorSolvedUnitTest.java b/core-java-modules/core-java-exceptions-3/src/test/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorSolvedUnitTest.java
new file mode 100644
index 0000000000..ad150334d4
--- /dev/null
+++ b/core-java-modules/core-java-exceptions-3/src/test/java/com/baeldung/exceptions/illegalaccesserror/IllegalAccessErrorSolvedUnitTest.java
@@ -0,0 +1,14 @@
+package com.baeldung.exceptions.illegalaccesserror;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class IllegalAccessErrorSolvedUnitTest {
+
+ @Test()
+ public void givenInterfaceDefaultMethOverriddenNonPrivateAccess_whenInvoked_thenNoIllegalAccessError() {
+ Assertions.assertDoesNotThrow(() -> {
+ new IllegalAccessErrorSolved().new MySubClassSolved().foobar();
+ });
+ }
+}
diff --git a/core-java-modules/core-java-io-4/src/main/java/com/baeldung/deserialization/vulnerabilities/BadThing.java b/core-java-modules/core-java-io-4/src/main/java/com/baeldung/deserialization/vulnerabilities/BadThing.java
new file mode 100644
index 0000000000..ce13a9c372
--- /dev/null
+++ b/core-java-modules/core-java-io-4/src/main/java/com/baeldung/deserialization/vulnerabilities/BadThing.java
@@ -0,0 +1,28 @@
+package com.baeldung.deserialization.vulnerabilities;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+public class BadThing implements Serializable {
+ private static final long serialVersionUID = 0L;
+
+ Object looselyDefinedThing;
+ String methodName;
+
+ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+ ois.defaultReadObject();
+ try {
+ Method method = looselyDefinedThing.getClass().getMethod(methodName);
+ method.invoke(looselyDefinedThing);
+ } catch (Exception e) {
+ // handle error...
+ }
+ }
+
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ oos.defaultWriteObject();
+ }
+}
diff --git a/core-java-modules/core-java-io-4/src/main/java/com/baeldung/deserialization/vulnerabilities/MyCustomAttackObject.java b/core-java-modules/core-java-io-4/src/main/java/com/baeldung/deserialization/vulnerabilities/MyCustomAttackObject.java
new file mode 100644
index 0000000000..9b4e2d4b76
--- /dev/null
+++ b/core-java-modules/core-java-io-4/src/main/java/com/baeldung/deserialization/vulnerabilities/MyCustomAttackObject.java
@@ -0,0 +1,14 @@
+package com.baeldung.deserialization.vulnerabilities;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+public class MyCustomAttackObject implements Serializable {
+ public static void methodThatTriggersAttack() {
+ try {
+ Runtime.getRuntime().exec("echo \"Oh, no! I've been hacked\"");
+ } catch (IOException e) {
+ // handle error...
+ }
+ }
+}
diff --git a/core-java-modules/core-java-io-4/src/test/java/com/baeldung/deserialization/vulnerabilities/BadThingUnitTest.java b/core-java-modules/core-java-io-4/src/test/java/com/baeldung/deserialization/vulnerabilities/BadThingUnitTest.java
new file mode 100644
index 0000000000..ea2180d178
--- /dev/null
+++ b/core-java-modules/core-java-io-4/src/test/java/com/baeldung/deserialization/vulnerabilities/BadThingUnitTest.java
@@ -0,0 +1,40 @@
+package com.baeldung.deserialization.vulnerabilities;
+
+import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+public class BadThingUnitTest {
+
+ @Test
+ @DisplayName("When a BadThing object is deserialized, then code execution in MyCustomAttackObject is run.")
+ public void givenABadThingObject_whenItsDeserialized_thenExecutionIsRun() throws Exception {
+ BadThing bt = new BadThing();
+
+ bt.looselyDefinedThing = new MyCustomAttackObject();
+ bt.methodName = "methodThatTriggersAttack";
+
+ byte[] serializedObject = serialize(bt);
+
+ try (InputStream bis = new ByteArrayInputStream(serializedObject);
+ ObjectInputStream ois = new ObjectInputStream(bis)) {
+
+ ois.readObject(); // malicious code is run
+ }
+ }
+
+ private static byte[] serialize(Object object) throws Exception {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+
+ oos.writeObject(object);
+ oos.flush();
+ return bos.toByteArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule2/pom.xml b/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule2/pom.xml
index 13d0b2d201..774319a067 100644
--- a/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule2/pom.xml
+++ b/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule2/pom.xml
@@ -45,4 +45,4 @@
1.0
-
\ No newline at end of file
+
diff --git a/core-java-modules/core-java-lang-4/README.md b/core-java-modules/core-java-lang-4/README.md
index 8b8dff4bd1..e1023513eb 100644
--- a/core-java-modules/core-java-lang-4/README.md
+++ b/core-java-modules/core-java-lang-4/README.md
@@ -5,3 +5,4 @@ This module contains articles about core features in the Java language
- [The Java final Keyword – Impact on Performance](https://www.baeldung.com/java-final-performance)
- [The package-info.java File](https://www.baeldung.com/java-package-info)
- [What are Compile-time Constants in Java?](https://www.baeldung.com/java-compile-time-constants)
+- [Java Objects.hash() vs Objects.hashCode()](https://www.baeldung.com/java-objects-hash-vs-objects-hashcode)
diff --git a/core-java-modules/core-java-lang-oop-patterns/README.md b/core-java-modules/core-java-lang-oop-patterns/README.md
index 178a556a96..df68a1413a 100644
--- a/core-java-modules/core-java-lang-oop-patterns/README.md
+++ b/core-java-modules/core-java-lang-oop-patterns/README.md
@@ -7,3 +7,4 @@ This module contains articles about Object-oriented programming (OOP) patterns i
- [Inheritance and Composition (Is-a vs Has-a relationship) in Java](https://www.baeldung.com/java-inheritance-composition)
- [Immutable Objects in Java](https://www.baeldung.com/java-immutable-object)
- [How to Make a Deep Copy of an Object in Java](https://www.baeldung.com/java-deep-copy)
+- [Using an Interface vs. Abstract Class in Java](https://www.baeldung.com/java-interface-vs-abstract-class)
diff --git a/core-java-modules/core-java-lang-operators-2/README.md b/core-java-modules/core-java-lang-operators-2/README.md
new file mode 100644
index 0000000000..46c874f361
--- /dev/null
+++ b/core-java-modules/core-java-lang-operators-2/README.md
@@ -0,0 +1,5 @@
+## Core Java Operators
+
+This module contains articles about Java operators
+
+## Relevant Articles:
diff --git a/core-java-modules/core-java-lang-operators-2/pom.xml b/core-java-modules/core-java-lang-operators-2/pom.xml
new file mode 100644
index 0000000000..1e7e659e6d
--- /dev/null
+++ b/core-java-modules/core-java-lang-operators-2/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+ core-java-lang-operators-2
+ 0.1.0-SNAPSHOT
+ core-java-lang-operators-2
+ jar
+
+
+ com.baeldung.core-java-modules
+ core-java-modules
+ 0.0.1-SNAPSHOT
+ ../
+
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
+
+
+
+ org.assertj
+ assertj-core
+ ${assertj-core.version}
+ test
+
+
+
+
+ core-java-lang-operators-2
+
+
+ src/main/resources
+ true
+
+
+
+
+
+
+ 3.10.0
+
+
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-lang-operators-2/src/test/java/com/baeldung/oroperators/BitwiseAndLogicalOROperatorUnitTest.java b/core-java-modules/core-java-lang-operators-2/src/test/java/com/baeldung/oroperators/BitwiseAndLogicalOROperatorUnitTest.java
new file mode 100644
index 0000000000..c04638991d
--- /dev/null
+++ b/core-java-modules/core-java-lang-operators-2/src/test/java/com/baeldung/oroperators/BitwiseAndLogicalOROperatorUnitTest.java
@@ -0,0 +1,4 @@
+package com.baeldung.oroperators;
+
+public class BitwiseAndLogicalOROperatorUnitTest {
+}
diff --git a/core-java-modules/core-java-networking-3/README.md b/core-java-modules/core-java-networking-3/README.md
index 09470fe88c..730231525f 100644
--- a/core-java-modules/core-java-networking-3/README.md
+++ b/core-java-modules/core-java-networking-3/README.md
@@ -5,4 +5,5 @@ This module contains articles about networking in Java
### Relevant Articles
- [Finding a Free Port in Java](https://www.baeldung.com/java-free-port)
+- [Downloading Email Attachments in Java](https://www.baeldung.com/java-download-email-attachments)
- [[<-- Prev]](/core-java-modules/core-java-networking-2)
diff --git a/core-java-modules/core-java-networking-3/pom.xml b/core-java-modules/core-java-networking-3/pom.xml
index 35e88b3b92..0ad800e173 100644
--- a/core-java-modules/core-java-networking-3/pom.xml
+++ b/core-java-modules/core-java-networking-3/pom.xml
@@ -36,16 +36,29 @@
${assertj.version}
test
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ com.sun.mail
+ javax.mail
+ 1.6.2
+
core-java-networking-3
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
diff --git a/core-java-modules/core-java-networking-3/src/main/java/com/baeldung/downloadattachments/DownloadEmailAttachments.java b/core-java-modules/core-java-networking-3/src/main/java/com/baeldung/downloadattachments/DownloadEmailAttachments.java
new file mode 100644
index 0000000000..4030f3b983
--- /dev/null
+++ b/core-java-modules/core-java-networking-3/src/main/java/com/baeldung/downloadattachments/DownloadEmailAttachments.java
@@ -0,0 +1,112 @@
+package com.baeldung.downloadattachments;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.NoSuchProviderException;
+import javax.mail.Part;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.internet.MimeBodyPart;
+
+public class DownloadEmailAttachments {
+ private String downloadDirectory;
+
+ public void setSaveDirectory(String dir) {
+ this.downloadDirectory = dir;
+ }
+
+ public void downloadEmailAttachments(String host, String port, String userName, String password) throws NoSuchProviderException, MessagingException, IOException {
+ Properties properties = setMailServerProperties(host, port);
+ Store store = setSessionStoreProperties(userName, password, properties);
+ Folder inbox = store.getFolder("INBOX");
+ inbox.open(Folder.READ_ONLY);
+ Message[] arrayMessages = inbox.getMessages();
+ for (int i = 0; i < arrayMessages.length; i++) {
+ Message message = arrayMessages[i];
+ Address[] fromAddress = message.getFrom();
+ String from = fromAddress[0].toString();
+ String subject = message.getSubject();
+ String sentDate = message.getSentDate().toString();
+ List attachments = new ArrayList();
+ if (message.getContentType().contains("multipart")) {
+ attachments = downloadAttachments(message);
+ }
+
+ System.out.println("Message #" + (i + 1) + ":");
+ System.out.println(" From: " + from);
+ System.out.println(" Subject: " + subject);
+ System.out.println(" Sent Date: " + sentDate);
+ System.out.println(" Attachments: " + attachments);
+ }
+ inbox.close(false);
+ store.close();
+ }
+
+ public List downloadAttachments(Message message) throws IOException, MessagingException {
+ List downloadedAttachments = new ArrayList();
+ Multipart multiPart = (Multipart) message.getContent();
+ int numberOfParts = multiPart.getCount();
+ for (int partCount = 0; partCount < numberOfParts; partCount++) {
+ MimeBodyPart part = (MimeBodyPart) multiPart.getBodyPart(partCount);
+ if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
+ String file = part.getFileName();
+ part.saveFile(downloadDirectory + File.separator + part.getFileName());
+ downloadedAttachments.add(file);
+ }
+ }
+
+ return downloadedAttachments;
+ }
+
+ public Store setSessionStoreProperties(String userName, String password, Properties properties) throws NoSuchProviderException, MessagingException {
+ Session session = Session.getDefaultInstance(properties);
+
+ Store store = session.getStore("pop3");
+ store.connect(userName, password);
+ return store;
+ }
+
+ public Properties setMailServerProperties(String host, String port) {
+ Properties properties = new Properties();
+
+ properties.put("mail.pop3.host", host);
+ properties.put("mail.pop3.port", port);
+
+ properties.setProperty("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+ properties.setProperty("mail.pop3.socketFactory.fallback", "false");
+ properties.setProperty("mail.pop3.socketFactory.port", String.valueOf(port));
+ return properties;
+ }
+
+ public static void main(String[] args) {
+ String host = "pop.gmail.com";
+ String port = "995";
+ String userName = "your_email";
+ String password = "your_password";
+
+ String saveDirectory = "valid_folder_path";
+
+ DownloadEmailAttachments receiver = new DownloadEmailAttachments();
+ receiver.setSaveDirectory(saveDirectory);
+ try {
+ receiver.downloadEmailAttachments(host, port, userName, password);
+ } catch (NoSuchProviderException ex) {
+ System.out.println("No provider for pop3.");
+ ex.printStackTrace();
+ } catch (MessagingException ex) {
+ System.out.println("Could not connect to the message store");
+ ex.printStackTrace();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+}
diff --git a/core-java-modules/core-java-networking-3/src/test/java/com/baeldung/downloadattachments/DownloadEmailAttachmentsLiveTest.java b/core-java-modules/core-java-networking-3/src/test/java/com/baeldung/downloadattachments/DownloadEmailAttachmentsLiveTest.java
new file mode 100644
index 0000000000..050790e6be
--- /dev/null
+++ b/core-java-modules/core-java-networking-3/src/test/java/com/baeldung/downloadattachments/DownloadEmailAttachmentsLiveTest.java
@@ -0,0 +1,25 @@
+package com.baeldung.downloadattachments;
+
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+public class DownloadEmailAttachmentsLiveTest {
+ @Test
+ public void when_Run_then_downloadAttachments() {
+
+ String host = "pop.gmail.com";
+ String port = "995";
+ String userName = "your_email";
+ String password = "your_password";
+
+ String saveDirectory = "valid_folder_path";
+
+ DownloadEmailAttachments receiver = new DownloadEmailAttachments();
+ receiver.setSaveDirectory(saveDirectory);
+ try {
+ receiver.downloadEmailAttachments(host, port, userName, password);
+ } catch (Exception ex) {
+ fail("Exception: " + ex);
+ }
+ }
+}
diff --git a/core-java-modules/core-java-reflection-2/README.md b/core-java-modules/core-java-reflection-2/README.md
index 3195cddc42..4c888bdf58 100644
--- a/core-java-modules/core-java-reflection-2/README.md
+++ b/core-java-modules/core-java-reflection-2/README.md
@@ -5,3 +5,4 @@
- [Checking If a Method is Static Using Reflection in Java](https://www.baeldung.com/java-check-method-is-static)
- [Checking if a Java Class is ‘abstract’ Using Reflection](https://www.baeldung.com/java-reflection-is-class-abstract)
- [Invoking a Private Method in Java](https://www.baeldung.com/java-call-private-method)
+- [Finding All Classes in a Java Package](https://www.baeldung.com/java-find-all-classes-in-package)
diff --git a/core-java-modules/core-java-reflection-2/pom.xml b/core-java-modules/core-java-reflection-2/pom.xml
index 74a7328c16..347f986275 100644
--- a/core-java-modules/core-java-reflection-2/pom.xml
+++ b/core-java-modules/core-java-reflection-2/pom.xml
@@ -22,6 +22,16 @@
${spring.version}
test
+
+ org.reflections
+ reflections
+ 0.9.12
+
+
+ com.google.guava
+ guava
+ 30.1.1-jre
+
diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/AccessingAllClassesInPackage.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/AccessingAllClassesInPackage.java
new file mode 100644
index 0000000000..dfbfaca270
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/AccessingAllClassesInPackage.java
@@ -0,0 +1,58 @@
+package com.baeldung.reflection.access.packages;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.reflections.Reflections;
+import org.reflections.scanners.SubTypesScanner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.reflect.ClassPath;
+
+@SuppressWarnings("rawtypes")
+public class AccessingAllClassesInPackage {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AccessingAllClassesInPackage.class);
+
+ public Set findAllClassesUsingClassLoader(String packageName) {
+ InputStream stream = ClassLoader.getSystemClassLoader()
+ .getResourceAsStream(packageName.replaceAll("[.]", "/"));
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+ return reader.lines()
+ .filter(line -> line.endsWith(".class"))
+ .map(line -> getClass(line, packageName))
+ .collect(Collectors.toSet());
+ }
+
+ private Class getClass(String className, String packageName) {
+ try {
+ return Class.forName(packageName + "." + className.substring(0, className.lastIndexOf('.')));
+ } catch (ClassNotFoundException e) {
+ LOG.error("<>");
+ }
+ return null;
+ }
+
+ public Set findAllClassesUsingReflectionsLibrary(String packageName) {
+ Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
+ return reflections.getSubTypesOf(Object.class)
+ .stream()
+ .collect(Collectors.toSet());
+ }
+
+ public Set findAllClassesUsingGoogleGuice(String packageName) throws IOException {
+ return ClassPath.from(ClassLoader.getSystemClassLoader())
+ .getAllClasses()
+ .stream()
+ .filter(clazz -> clazz.getPackageName()
+ .equalsIgnoreCase(packageName))
+ .map(clazz -> clazz.load())
+ .collect(Collectors.toSet());
+ }
+
+}
diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/ClassExample.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/ClassExample.java
new file mode 100644
index 0000000000..d1ff027770
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/ClassExample.java
@@ -0,0 +1,6 @@
+package com.baeldung.reflection.access.packages.search;
+
+public class ClassExample {
+ class NestedClassExample {
+ }
+}
diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/InterfaceExample.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/InterfaceExample.java
new file mode 100644
index 0000000000..340c552da8
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/InterfaceExample.java
@@ -0,0 +1,4 @@
+package com.baeldung.reflection.access.packages.search;
+
+public interface InterfaceExample {
+}
diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/Searchable.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/Searchable.java
new file mode 100644
index 0000000000..6f4180a136
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/Searchable.java
@@ -0,0 +1,5 @@
+package com.baeldung.reflection.access.packages.search;
+
+public @interface Searchable {
+
+}
diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/package-info.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/package-info.java
new file mode 100644
index 0000000000..f920000b92
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/packages/search/package-info.java
@@ -0,0 +1,2 @@
+@Searchable
+package com.baeldung.reflection.access.packages.search;
\ No newline at end of file
diff --git a/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/packages/AccessingAllClassesInPackageUnitTest.java b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/packages/AccessingAllClassesInPackageUnitTest.java
new file mode 100644
index 0000000000..5bee2c0f3e
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/packages/AccessingAllClassesInPackageUnitTest.java
@@ -0,0 +1,39 @@
+package com.baeldung.reflection.access.packages;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.rules.ExpectedException;
+
+@SuppressWarnings("rawtypes")
+public class AccessingAllClassesInPackageUnitTest {
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ private static final String PACKAGE_NAME = "com.baeldung.reflection.access.packages.search";
+
+ @Test
+ public void when_findAllClassesUsingClassLoader_thenSuccess() {
+ AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
+ Set classes = instance.findAllClassesUsingClassLoader(PACKAGE_NAME);
+ Assertions.assertEquals(5, classes.size());
+ }
+
+ @Test
+ public void when_findAllClassesUsingReflectionsLibrary_thenSuccess() {
+ AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
+ Set classes = instance.findAllClassesUsingReflectionsLibrary(PACKAGE_NAME);
+ Assertions.assertEquals(5, classes.size());
+ }
+
+ @Test
+ public void when_findAllClassesUsingGoogleGuice_thenSuccess() throws IOException {
+ AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
+ Set classes = instance.findAllClassesUsingGoogleGuice(PACKAGE_NAME);
+ Assertions.assertEquals(5, classes.size());
+ }
+
+}
diff --git a/core-java-modules/core-java-regex/README.md b/core-java-modules/core-java-regex/README.md
index 92321fa656..bc28f4b732 100644
--- a/core-java-modules/core-java-regex/README.md
+++ b/core-java-modules/core-java-regex/README.md
@@ -13,3 +13,4 @@
- [Regular Expressions \s and \s+ in Java](https://www.baeldung.com/java-regex-s-splus)
- [Validate Phone Numbers With Java Regex](https://www.baeldung.com/java-regex-validate-phone-numbers)
- [How to Count the Number of Matches for a Regex?](https://www.baeldung.com/java-count-regex-matches)
+- [Find All Numbers in a String in Java](https://www.baeldung.com/java-find-numbers-in-string)
diff --git a/core-java-modules/core-java-regex/src/test/java/com/baeldung/ignore/pattern/metacharacters/IgnoringPatternMetacharactersUnitTest.java b/core-java-modules/core-java-regex/src/test/java/com/baeldung/ignore/pattern/metacharacters/IgnoringPatternMetacharactersUnitTest.java
new file mode 100644
index 0000000000..921876c0d5
--- /dev/null
+++ b/core-java-modules/core-java-regex/src/test/java/com/baeldung/ignore/pattern/metacharacters/IgnoringPatternMetacharactersUnitTest.java
@@ -0,0 +1,54 @@
+package com.baeldung.ignore.pattern.metacharacters;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+public class IgnoringPatternMetacharactersUnitTest {
+ private static final String dollarAmounts = "$100.25, $100.50, $150.50, $100.50, $100.75";
+ private static final String patternStr = "$100.50";
+
+ @Test
+ public void givenPatternStringHasMetacharacters_whenPatternMatchedWithoutEscapingMetacharacters_thenNoMatchesFound() {
+ Pattern pattern = Pattern.compile(patternStr);
+ Matcher matcher = pattern.matcher(dollarAmounts);
+
+ int matches = 0;
+ while (matcher.find()) {
+ matches++;
+ }
+
+ assertEquals(0, matches);
+ }
+
+ @Test
+ public void givenPatternStringHasMetacharacters_whenPatternCompiledUsingManuallyMetaEscapedPattern_thenMatchingSuccessful() {
+ String metaEscapedPatternStr = "\\Q" + patternStr + "\\E";
+ Pattern pattern = Pattern.compile(metaEscapedPatternStr);
+ Matcher matcher = pattern.matcher(dollarAmounts);
+
+ int matches = 0;
+ while (matcher.find()) {
+ matches++;
+ }
+
+ assertEquals(2, matches);
+ }
+
+ @Test
+ public void givenPatternStringHasMetacharacters_whenPatternCompiledUsingLiteralPatternFromQuote_thenMatchingSuccessful() {
+ String literalPatternStr = Pattern.quote(patternStr);
+ Pattern pattern = Pattern.compile(literalPatternStr);
+ Matcher matcher = pattern.matcher(dollarAmounts);
+
+ int matches = 0;
+ while (matcher.find()) {
+ matches++;
+ }
+
+ assertEquals(2, matches);
+ }
+}
diff --git a/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/countdigits/CountDigitsUnitTest.java b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/countdigits/CountDigitsUnitTest.java
new file mode 100644
index 0000000000..e90d2e9f26
--- /dev/null
+++ b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/countdigits/CountDigitsUnitTest.java
@@ -0,0 +1,77 @@
+package com.baeldung.regex.countdigits;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.common.base.CharMatcher;
+
+/**
+ * Unit Test to count the number of digits in a String
+ */
+class CountDigitsUnitTest {
+
+ // Guava CharMatcher to match digits
+ private static final CharMatcher DIGIT_CHAR_MATCHER = CharMatcher.inRange('0', '9');
+
+ private static final String STR_WITH_ALL_DIGITS = "970987678607608";
+ private static final String STR_WITH_SINGLE_DIGITS_SEP_BY_NON_DIGITS = "9kjl()4f*(&6~3dfd8&5dfd8a";
+ private static final String STR_WITH_SEQUENCES_OF_1_OR_MORE_DIGITS_SEP_BY_NON_DIGITS
+ = "64.6lk.l~453lkdsf9wg038.68*()(k;95786fsd7986";
+
+ private static int countDigits(String stringToSearch) {
+ Matcher countEmailMatcher = Pattern.compile("\\d").matcher(stringToSearch);
+
+ int count = 0;
+ while (countEmailMatcher.find()) {
+ count++;
+ }
+
+ return count;
+ }
+
+ @Test
+ void givenStrOfAllDigits_whenRegexMatchByDigit_thenFifteenDigitsCounted() {
+ int count = countDigits(STR_WITH_ALL_DIGITS);
+
+ assertThat(count).isEqualTo(15);
+ }
+
+ @Test
+ void givenStrWithSingleDigitsSepByNonDigits_whenRegexMatchByDigit_thenSevenDigitsCounted() {
+ int count = countDigits(STR_WITH_SINGLE_DIGITS_SEP_BY_NON_DIGITS);
+
+ assertThat(count).isEqualTo(7);
+ }
+
+ @Test
+ void givenStrWithOneOrMoreDigitsSepByNonDigits_whenRegexMatchByDigit_thenTwentyOneDigitsCounted() {
+ int count = countDigits(STR_WITH_SEQUENCES_OF_1_OR_MORE_DIGITS_SEP_BY_NON_DIGITS);
+
+ assertThat(count).isEqualTo(21);
+ }
+
+ @Test
+ void givenStrOfAllDigits_whenGuavaCharMatchByDigit_thenFifteenDigitsCounted() {
+ int count = DIGIT_CHAR_MATCHER.countIn(STR_WITH_ALL_DIGITS);
+
+ assertThat(count).isEqualTo(15);
+ }
+
+ @Test
+ void givenStrWithSingleDigitsSepByNonDigits_whenGuavaCharMatchByDigit_thenSevenDigitsCounted() {
+ int count = DIGIT_CHAR_MATCHER.countIn(STR_WITH_SINGLE_DIGITS_SEP_BY_NON_DIGITS);
+
+ assertThat(count).isEqualTo(7);
+ }
+
+ @Test
+ void givenStrWithOneOrMoreDigitsSepByNonDigits_whenGuavaCharMatchByDigit_thenTwentyOneDigitsCounted() {
+ int count = DIGIT_CHAR_MATCHER.countIn(STR_WITH_SEQUENCES_OF_1_OR_MORE_DIGITS_SEP_BY_NON_DIGITS);
+
+ assertThat(count).isEqualTo(21);
+ }
+}
diff --git a/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/findnumbers/FindNumbersUnitTest.java b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/findnumbers/FindNumbersUnitTest.java
new file mode 100644
index 0000000000..128a326ae2
--- /dev/null
+++ b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/findnumbers/FindNumbersUnitTest.java
@@ -0,0 +1,128 @@
+package com.baeldung.regex.findnumbers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.DoubleStream;
+import java.util.stream.LongStream;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit Test to find Integers, Decimal Numbers, and Scientific Notation and Hexadecimal Numbers in a String
+ */
+class FindNumbersUnitTest {
+
+ private static List findIntegers(String stringToSearch) {
+ Pattern integerPattern = Pattern.compile("-?\\d+");
+ Matcher matcher = integerPattern.matcher(stringToSearch);
+
+ List integerList = new ArrayList<>();
+ while (matcher.find()) {
+ integerList.add(matcher.group());
+ }
+
+ return integerList;
+ }
+
+ private static List findDecimalNums(String stringToSearch) {
+ Pattern decimalNumPattern = Pattern.compile("-?\\d+(\\.\\d+)?");
+ Matcher matcher = decimalNumPattern.matcher(stringToSearch);
+
+ List decimalNumList = new ArrayList<>();
+ while (matcher.find()) {
+ decimalNumList.add(matcher.group());
+ }
+
+ return decimalNumList;
+ }
+
+ @Test
+ void givenStrOfAllDigits_whenRegexMatchByInt_thenWholeStrMatchedAsOneInt() {
+ List integersFound = findIntegers("970987678607608");
+
+ assertThat(integersFound).containsExactly("970987678607608");
+ }
+
+ @Test
+ void givenStrWithIntegersSepByPeriods_whenRegexMatchByInt_thenExpectedIntsFound() {
+ List integersFound = findIntegers("3453..5.-23532...32432.-2363.3454......345.-34.");
+
+ assertThat(integersFound).containsExactly("3453", "5", "-23532", "32432", "-2363", "3454", "345", "-34");
+ }
+
+ @Test
+ void givenStrWithIntegersSepByNonDigits_whenRegexMatchByInt_thenExpectedIntsFound() {
+ List integersFound = findIntegers("646lkl~4-53l-k34.fdsf.-ds-35.45f9wg3868*()(k;-95786fsd79-86");
+
+ assertThat(integersFound).containsExactly("646", "4", "-53", "34", "-35", "45", "9", "3868", "-95786", "79", "-86");
+ }
+
+ @Test
+ void givenStrOfAllDigits_whenRegexMatchByDecNum_thenWholeStrMatchedAsOneDecimalNumber() {
+ List decimalNumsFound = findDecimalNums("970987678607608");
+
+ assertThat(decimalNumsFound).containsExactly("970987678607608");
+ }
+
+ @Test
+ void givenStrOfDecNumsSepByNonDigits_whenRegexMatchByDecNum_thenExpectedNumsFound() {
+ List decimalNumsFound = findDecimalNums(".7854.455wo.rdy(do.g)-3.-553.00.53;good^night%o3456sdcardR%3567.4%£cat");
+
+ assertThat(decimalNumsFound).containsExactly("7854.455", "-3", "-553.00", "53", "3456", "3567.4");
+ }
+
+ @Test
+ void givenStrWithRandomDigitsDashesAndPeriods_whenRegexMatchByDecNum_thenExpectedNumsFound() {
+ List decimalNumsFound = findDecimalNums(".-..90834.345.--493-..-85.-875.345-.-.-355.345...345.-.636-5.6-3.");
+
+ assertThat(decimalNumsFound).containsExactly("90834.345", "-493", "-85", "-875.345", "-355.345", "345", "636", "-5.6", "-3");
+ }
+
+ @Test
+ void givenStrOfIntsSepByNonDigits_whenRegexMatchByInt_thenExpectedValuesFound() {
+ LongStream integerValuesFound = findIntegers(".7854.455wo.rdy(do.g)-3.ght%o34.56")
+ .stream().mapToLong(Long::valueOf);
+
+ assertThat(integerValuesFound).containsExactly(7854L, 455L, -3L, 34L, 56L);
+ }
+
+ @Test
+ void givenStrOfDecNumsSepByNonDigits_whenRegexMatchByDecNum_thenExpectedValuesFound() {
+ DoubleStream decimalNumValuesFound = findDecimalNums(".7854.455wo.rdy(do.g)-3.ght%o34.56")
+ .stream().mapToDouble(Double::valueOf);
+
+ assertThat(decimalNumValuesFound).containsExactly(7854.455, -3.0, 34.56);
+ }
+
+ @Test
+ void givenStrOfSciNotationNumsSepByNonDigits_whenRegexMatchBySciNotNum_thenExpectedNumsFound() {
+ String strToSearch = "}s1.25E-3>,/@l2e109he-70.96E+105d£d_-8.7312E-102=#;,.d919.3822e+31e]";
+
+ Matcher matcher = Pattern.compile("-?\\d+(\\.\\d+)?[eE][+-]?\\d+")
+ .matcher(strToSearch);
+ List sciNotationNums = new ArrayList<>();
+ while (matcher.find()) {
+ sciNotationNums.add(matcher.group());
+ }
+
+ assertThat(sciNotationNums).containsExactly("1.25E-3", "2e109", "-70.96E+105", "-8.7312E-102", "919.3822e+31");
+ }
+
+ @Test
+ void givenStrOfHexNumsSepByNonDigits_whenRegexMatchByHexNum_thenExpectedNumsFound() {
+ String strToSearch = "}saF851Bq-3f6Cm>,/@j-2Ad9eE>70ae19.>";
+
+ Matcher matcher = Pattern.compile("-?[0-9a-fA-F]+")
+ .matcher(strToSearch);
+ List hexNums = new ArrayList<>();
+ while (matcher.find()) {
+ hexNums.add(matcher.group());
+ }
+
+ assertThat(hexNums).containsExactly("aF851B", "-3f6C", "-2Ad9eE", "70ae19");
+ }
+}
diff --git a/core-java-modules/core-java-security-3/README.md b/core-java-modules/core-java-security-3/README.md
new file mode 100644
index 0000000000..970faaac88
--- /dev/null
+++ b/core-java-modules/core-java-security-3/README.md
@@ -0,0 +1,8 @@
+## Core Java Security
+
+This module contains articles about core Java Security
+
+### Relevant Articles:
+
+- [Secret Key and String Conversion in Java](https://www.baeldung.com/java-secret-key-to-string)
+- More articles: [[<-- prev]](/core-java-modules/core-java-security-2)
diff --git a/core-java-modules/core-java-security-3/pom.xml b/core-java-modules/core-java-security-3/pom.xml
new file mode 100644
index 0000000000..2520cee7f8
--- /dev/null
+++ b/core-java-modules/core-java-security-3/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+ core-java-security-2
+ 0.1.0-SNAPSHOT
+ core-java-security-2
+ jar
+
+
+ com.baeldung.core-java-modules
+ core-java-modules
+ 0.0.1-SNAPSHOT
+ ../
+
+
+
+
+ commons-codec
+ commons-codec
+ ${commons-codec.version}
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ ${bouncycastle.version}
+
+
+
+ org.assertj
+ assertj-core
+ ${assertj-core.version}
+ test
+
+
+
+ javax.xml.bind
+ jaxb-api
+ ${jaxb-api.version}
+
+
+
+
+
+ 1.60
+ 1.11
+
+ 3.18.0
+ 2.3.1
+
+
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-security-3/src/main/java/com/baeldung/secretkeyandstringconversion/ConversionClassUtil.java b/core-java-modules/core-java-security-3/src/main/java/com/baeldung/secretkeyandstringconversion/ConversionClassUtil.java
new file mode 100644
index 0000000000..8fdf682666
--- /dev/null
+++ b/core-java-modules/core-java-security-3/src/main/java/com/baeldung/secretkeyandstringconversion/ConversionClassUtil.java
@@ -0,0 +1,53 @@
+package com.baeldung.secretkeyandstringconversion;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Base64;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class ConversionClassUtil {
+
+ /* Generating Secret key */
+
+ // Generating Secret Key using KeyGenerator class with 256
+ public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
+ keyGenerator.init(n);
+ SecretKey originalKey = keyGenerator.generateKey();
+ return originalKey;
+ }
+
+ // Generating Secret Key using password and salt
+ public static SecretKey getKeyFromPassword(String password, String salt)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+ KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
+ SecretKey originalKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
+ return originalKey;
+ }
+
+ /* Converting Secret key into String */
+ public static String convertSecretKeyToString(SecretKey secretKey) throws NoSuchAlgorithmException {
+ // Converting the Secret Key into byte array
+ byte[] rawData = secretKey.getEncoded();
+ // Getting String - Base64 encoded version of the Secret Key
+ String encodedKey = Base64.getEncoder().encodeToString(rawData);
+ return encodedKey;
+ }
+
+ /* Converting String into Secret key into */
+ public static SecretKey convertStringToSecretKeyto(String encodedKey) {
+ // Decoding the Base64 encoded string into byte array
+ byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
+ // Rebuilding the Secret Key using SecretKeySpec Class
+ SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
+ return originalKey;
+ }
+
+}
diff --git a/core-java-modules/core-java-security-3/src/test/java/com/baeldung/secretkeyandstringconversion/ConversionClassUtilUnitTest.java b/core-java-modules/core-java-security-3/src/test/java/com/baeldung/secretkeyandstringconversion/ConversionClassUtilUnitTest.java
new file mode 100644
index 0000000000..7a912dbf26
--- /dev/null
+++ b/core-java-modules/core-java-security-3/src/test/java/com/baeldung/secretkeyandstringconversion/ConversionClassUtilUnitTest.java
@@ -0,0 +1,44 @@
+package com.baeldung.secretkeyandstringconversion;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.SecretKey;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ConversionClassUtilUnitTest {
+
+ @Test
+ void givenPasswordAndSalt_whenCreateSecreKeyCheckConversion_thenSuccess()
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ // given
+ String password = "Baeldung@2021";
+ String salt = "@$#baelDunG@#^$*";
+
+ // when
+ SecretKey encodedKey = ConversionClassUtil.getKeyFromPassword(password, salt);
+ String encodedString = ConversionClassUtil.convertSecretKeyToString(encodedKey);
+ SecretKey decodeKey = ConversionClassUtil.convertStringToSecretKeyto(encodedString);
+
+ // then
+ Assertions.assertEquals(encodedKey, decodeKey);
+ }
+
+ @Test
+ void givenSize_whenCreateSecreKeyCheckConversion_thenSuccess()
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ // given
+ int size = 256;
+
+ // when
+ SecretKey encodedKey = ConversionClassUtil.generateKey(size);
+ String encodedString = ConversionClassUtil.convertSecretKeyToString(encodedKey);
+ SecretKey decodeKey = ConversionClassUtil.convertStringToSecretKeyto(encodedString);
+
+ // then
+ Assertions.assertEquals(encodedKey, decodeKey);
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/README.md b/core-java-modules/core-java-streams-3/README.md
index 26b4dfe975..48ebf145d2 100644
--- a/core-java-modules/core-java-streams-3/README.md
+++ b/core-java-modules/core-java-streams-3/README.md
@@ -12,4 +12,5 @@ This module contains articles about the Stream API in Java.
- [Should We Close a Java Stream?](https://www.baeldung.com/java-stream-close)
- [Returning Stream vs. Collection](https://www.baeldung.com/java-return-stream-collection)
- [Convert a Java Enumeration Into a Stream](https://www.baeldung.com/java-enumeration-to-stream)
+- [When to Use a Parallel Stream in Java](https://www.baeldung.com/java-when-to-use-parallel-stream)
- More articles: [[<-- prev>]](/../core-java-streams-2)
diff --git a/core-java-modules/core-java-streams-3/pom.xml b/core-java-modules/core-java-streams-3/pom.xml
index 659f1937f2..01b83f229a 100644
--- a/core-java-modules/core-java-streams-3/pom.xml
+++ b/core-java-modules/core-java-streams-3/pom.xml
@@ -27,6 +27,17 @@
${lombok.version}
provided
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+ test
+
org.assertj
@@ -44,11 +55,30 @@
true
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+
+
+
+
+
+ 1.18.20
3.6.1
+ 1.29
\ No newline at end of file
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/BenchmarkRunner.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/BenchmarkRunner.java
new file mode 100644
index 0000000000..461d728ad0
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/BenchmarkRunner.java
@@ -0,0 +1,9 @@
+package com.baeldung.streams.parallel;
+
+public class BenchmarkRunner {
+
+ public static void main(String[] args) throws Exception {
+ org.openjdk.jmh.Main.main(args);
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/DifferentSourceSplitting.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/DifferentSourceSplitting.java
new file mode 100644
index 0000000000..9ad569df30
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/DifferentSourceSplitting.java
@@ -0,0 +1,54 @@
+package com.baeldung.streams.parallel;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+public class DifferentSourceSplitting {
+
+ private static final List arrayListOfNumbers = new ArrayList<>();
+ private static final List linkedListOfNumbers = new LinkedList<>();
+
+ static {
+ IntStream.rangeClosed(1, 1_000_000).forEach(i -> {
+ arrayListOfNumbers.add(i);
+ linkedListOfNumbers.add(i);
+ });
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void differentSourceArrayListSequential() {
+ arrayListOfNumbers.stream().reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void differentSourceArrayListParallel() {
+ arrayListOfNumbers.parallelStream().reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void differentSourceLinkedListSequential() {
+ linkedListOfNumbers.stream().reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void differentSourceLinkedListParallel() {
+ linkedListOfNumbers.parallelStream().reduce(0, Integer::sum);
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/MemoryLocalityCosts.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/MemoryLocalityCosts.java
new file mode 100644
index 0000000000..bc5cbf491b
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/MemoryLocalityCosts.java
@@ -0,0 +1,52 @@
+package com.baeldung.streams.parallel;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+public class MemoryLocalityCosts {
+
+ private static final int[] intArray = new int[1_000_000];
+ private static final Integer[] integerArray = new Integer[1_000_000];
+
+ static {
+ IntStream.rangeClosed(1, 1_000_000).forEach(i -> {
+ intArray[i-1] = i;
+ integerArray[i-1] = i;
+ });
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void localityIntArraySequential() {
+ Arrays.stream(intArray).reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void localityIntArrayParallel() {
+ Arrays.stream(intArray).parallel().reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void localityIntegerArraySequential() {
+ Arrays.stream(integerArray).reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void localityIntegerArrayParallel() {
+ Arrays.stream(integerArray).parallel().reduce(0, Integer::sum);
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/MergingCosts.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/MergingCosts.java
new file mode 100644
index 0000000000..a9919dbe72
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/MergingCosts.java
@@ -0,0 +1,52 @@
+package com.baeldung.streams.parallel;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class MergingCosts {
+
+ private static final List arrayListOfNumbers = new ArrayList<>();
+
+ static {
+ IntStream.rangeClosed(1, 1_000_000).forEach(i -> {
+ arrayListOfNumbers.add(i);
+ });
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void mergingCostsSumSequential() {
+ arrayListOfNumbers.stream().reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void mergingCostsSumParallel() {
+ arrayListOfNumbers.stream().parallel().reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void mergingCostsGroupingSequential() {
+ arrayListOfNumbers.stream().collect(Collectors.toSet());
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void mergingCostsGroupingParallel() {
+ arrayListOfNumbers.stream().parallel().collect(Collectors.toSet());
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/ParallelStream.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/ParallelStream.java
new file mode 100644
index 0000000000..f236f418e8
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/ParallelStream.java
@@ -0,0 +1,15 @@
+package com.baeldung.streams.parallel;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ParallelStream {
+
+ public static void main(String[] args) {
+ List listOfNumbers = Arrays.asList(1, 2, 3, 4);
+ listOfNumbers.parallelStream().forEach(number ->
+ System.out.println(number + " " + Thread.currentThread().getName())
+ );
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/SequentialStream.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/SequentialStream.java
new file mode 100644
index 0000000000..01379130fa
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/SequentialStream.java
@@ -0,0 +1,15 @@
+package com.baeldung.streams.parallel;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class SequentialStream {
+
+ public static void main(String[] args) {
+ List listOfNumbers = Arrays.asList(1, 2, 3, 4);
+ listOfNumbers.stream().forEach(number ->
+ System.out.println(number + " " + Thread.currentThread().getName())
+ );
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/SplittingCosts.java b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/SplittingCosts.java
new file mode 100644
index 0000000000..d1e878df1f
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/main/java/com/baeldung/streams/parallel/SplittingCosts.java
@@ -0,0 +1,27 @@
+package com.baeldung.streams.parallel;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+public class SplittingCosts {
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void sourceSplittingIntStreamSequential() {
+ IntStream.rangeClosed(1, 100).reduce(0, Integer::sum);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public static void sourceSplittingIntStreamParallel() {
+ IntStream.rangeClosed(1, 100).parallel().reduce(0, Integer::sum);
+ }
+
+}
diff --git a/core-java-modules/core-java-streams-3/src/test/java/com/baeldung/streams/parallel/ForkJoinUnitTest.java b/core-java-modules/core-java-streams-3/src/test/java/com/baeldung/streams/parallel/ForkJoinUnitTest.java
new file mode 100644
index 0000000000..f9aab8ed6c
--- /dev/null
+++ b/core-java-modules/core-java-streams-3/src/test/java/com/baeldung/streams/parallel/ForkJoinUnitTest.java
@@ -0,0 +1,46 @@
+package com.baeldung.streams.parallel;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ForkJoinUnitTest {
+
+ @Test
+ void givenSequentialStreamOfNumbers_whenReducingSumWithIdentityFive_thenResultIsCorrect() {
+ List listOfNumbers = Arrays.asList(1, 2, 3, 4);
+ int sum = listOfNumbers.stream().reduce(5, Integer::sum);
+ assertThat(sum).isEqualTo(15);
+ }
+
+ @Test
+ void givenParallelStreamOfNumbers_whenReducingSumWithIdentityFive_thenResultIsNotCorrect() {
+ List listOfNumbers = Arrays.asList(1, 2, 3, 4);
+ int sum = listOfNumbers.parallelStream().reduce(5, Integer::sum);
+ assertThat(sum).isNotEqualTo(15);
+ }
+
+ @Test
+ void givenParallelStreamOfNumbers_whenReducingSumWithIdentityZero_thenResultIsCorrect() {
+ List listOfNumbers = Arrays.asList(1, 2, 3, 4);
+ int sum = listOfNumbers.parallelStream().reduce(0, Integer::sum) + 5;
+ assertThat(sum).isEqualTo(15);
+ }
+
+ @Test
+ public void givenParallelStreamOfNumbers_whenUsingCustomThreadPool_thenResultIsCorrect()
+ throws InterruptedException, ExecutionException {
+ List listOfNumbers = Arrays.asList(1, 2, 3, 4);
+ ForkJoinPool customThreadPool = new ForkJoinPool(4);
+ int sum = customThreadPool.submit(
+ () -> listOfNumbers.parallelStream().reduce(0, Integer::sum)).get();
+ customThreadPool.shutdown();
+ assertThat(sum).isEqualTo(10);
+ }
+
+}
diff --git a/core-java-modules/core-java-string-conversions-2/README.md b/core-java-modules/core-java-string-conversions-2/README.md
index afdd7e5760..3bd3ba927e 100644
--- a/core-java-modules/core-java-string-conversions-2/README.md
+++ b/core-java-modules/core-java-string-conversions-2/README.md
@@ -6,4 +6,5 @@ This module contains articles about string conversions from/to another type.
- [Java String Conversions](https://www.baeldung.com/java-string-conversions)
- [Convert String to Byte Array and Reverse in Java](https://www.baeldung.com/java-string-to-byte-array)
- [Convert Character Array to String in Java](https://www.baeldung.com/java-char-array-to-string)
+- [Converting String to BigDecimal in Java](https://www.baeldung.com/java-string-to-bigdecimal)
- More articles: [[<-- prev]](/core-java-string-conversions)
diff --git a/core-java-modules/core-java-string-conversions-2/src/test/java/com/baeldung/stringtobigdecimal/StringToBigDecimalConversionUnitTest.java b/core-java-modules/core-java-string-conversions-2/src/test/java/com/baeldung/stringtobigdecimal/StringToBigDecimalConversionUnitTest.java
new file mode 100644
index 0000000000..cd8ef6c70f
--- /dev/null
+++ b/core-java-modules/core-java-string-conversions-2/src/test/java/com/baeldung/stringtobigdecimal/StringToBigDecimalConversionUnitTest.java
@@ -0,0 +1,74 @@
+package com.baeldung.stringtobigdecimal;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+
+import org.junit.Test;
+
+public class StringToBigDecimalConversionUnitTest {
+
+ @Test
+ public void givenValidString_WhenBigDecimalObjectWithStringParameter_ThenResultIsDecimalObject() {
+ BigDecimal bigDecimal = new BigDecimal("123");
+ assertEquals(new BigDecimal(123), bigDecimal);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void givenNullString_WhenBigDecimalObjectWithStringParameter_ThenNullPointerExceptionIsThrown() {
+ String bigDecimal = null;
+ new BigDecimal(bigDecimal);
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void givenInalidString_WhenBigDecimalObjectWithStringParameter_ThenNumberFormatExceptionIsThrown() {
+ new BigDecimal("&");
+ }
+
+ @Test
+ public void givenValidString_WhenValueOfDoubleFromString_ThenResultIsDecimalObject() {
+ BigDecimal bigDecimal = BigDecimal.valueOf(Double.valueOf("123.42"));
+ assertEquals(new BigDecimal(123.42).setScale(2, BigDecimal.ROUND_HALF_UP), bigDecimal);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void givenNullString_WhenValueOfDoubleFromString_ThenNullPointerExceptionIsThrown() {
+ BigDecimal.valueOf(Double.valueOf(null));
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void givenInalidString_WhenValueOfDoubleFromString_ThenNumberFormatExceptionIsThrown() {
+ BigDecimal.valueOf(Double.valueOf("&"));
+ }
+
+ @Test
+ public void givenValidString_WhenDecimalFormatOfString_ThenResultIsDecimalObject() throws ParseException {
+ BigDecimal bigDecimal = new BigDecimal(10692467440017.111).setScale(3, BigDecimal.ROUND_HALF_UP);
+
+ DecimalFormatSymbols symbols = new DecimalFormatSymbols();
+ symbols.setGroupingSeparator(',');
+ symbols.setDecimalSeparator('.');
+ String pattern = "#,##0.0#";
+ DecimalFormat decimalFormat = new DecimalFormat(pattern, symbols);
+ decimalFormat.setParseBigDecimal(true);
+
+ // parse the string value
+ BigDecimal parsedStringValue = (BigDecimal) decimalFormat.parse("10,692,467,440,017.111");
+
+ assertEquals(bigDecimal, parsedStringValue);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void givenNullString_WhenDecimalFormatOfString_ThenNullPointerExceptionIsThrown() throws ParseException {
+ new DecimalFormat("#").parse(null);
+ }
+
+ @Test(expected = ParseException.class)
+ public void givenInalidString_WhenDecimalFormatOfString_ThenNumberFormatExceptionIsThrown() throws ParseException {
+ new DecimalFormat("#").parse("&");
+ }
+
+}
diff --git a/core-java-modules/core-java-string-operations-3/README.md b/core-java-modules/core-java-string-operations-3/README.md
index bc4af852ed..ad4ada3a68 100644
--- a/core-java-modules/core-java-string-operations-3/README.md
+++ b/core-java-modules/core-java-string-operations-3/README.md
@@ -3,3 +3,4 @@
- [Version Comparison in Java](https://www.baeldung.com/java-comparing-versions)
- [Java (String) or .toString()?](https://www.baeldung.com/java-string-casting-vs-tostring)
- [Split Java String by Newline](https://www.baeldung.com/java-string-split-by-newline)
+- [Split a String in Java and Keep the Delimiters](https://www.baeldung.com/java-split-string-keep-delimiters)
diff --git a/core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/splitkeepdelimiters/SplitAndKeepDelimitersUnitTest.java b/core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/splitkeepdelimiters/SplitAndKeepDelimitersUnitTest.java
new file mode 100644
index 0000000000..ede8be4c05
--- /dev/null
+++ b/core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/splitkeepdelimiters/SplitAndKeepDelimitersUnitTest.java
@@ -0,0 +1,59 @@
+package com.baeldung.splitkeepdelimiters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.base.Splitter;
+
+public class SplitAndKeepDelimitersUnitTest {
+
+ private final String positivelookAheadRegex = "((?=@))";
+ private final String positivelookBehindRegex = "((?<=@))";
+ private final String positivelookAroundRegex = "((?=@)|(?<=@))";
+ private final String positiveLookAroundMultiDelimiterRegex = "((?=:|#|@)|(?<=:|#|@))";
+
+ private String text = "Hello@World@This@Is@A@Java@Program";
+ private String textMixed = "@HelloWorld@This:Is@A#Java#Program";
+ private String textMixed2 = "pg@no;10@hello;world@this;is@a#10words;Java#Program";
+
+ @Test
+ public void givenString_splitAndKeepDelimiters_using_javaLangString() {
+
+ assertThat(text.split(positivelookAheadRegex)).containsExactly("Hello", "@World", "@This", "@Is", "@A", "@Java", "@Program");
+
+ assertThat(text.split(positivelookBehindRegex)).containsExactly("Hello@", "World@", "This@", "Is@", "A@", "Java@", "Program");
+
+ assertThat(text.split(positivelookAroundRegex)).containsExactly("Hello", "@", "World", "@", "This", "@", "Is", "@", "A", "@", "Java", "@", "Program");
+
+ assertThat(textMixed.split(positiveLookAroundMultiDelimiterRegex)).containsExactly("@", "HelloWorld", "@", "This", ":", "Is", "@", "A", "#", "Java", "#", "Program");
+
+ }
+
+ @Test
+ public void givenString_splitAndKeepDelimiters_using_ApacheCommonsLang3StringUtils() {
+
+ assertThat(StringUtils.splitByCharacterType(textMixed2)).containsExactly("pg", "@", "no", ";", "10", "@", "hello", ";", "world", "@", "this", ";", "is", "@", "a", "#", "10", "words", ";", "J", "ava", "#", "P", "rogram");
+
+ }
+
+ @Test
+ public void givenString_splitAndKeepDelimiters_using_GuavaSplitter() {
+
+ assertThat(Splitter.onPattern(positivelookAroundRegex)
+ .splitToList(text)).containsExactly("Hello", "@", "World", "@", "This", "@", "Is", "@", "A", "@", "Java", "@", "Program");
+
+ assertThat(Splitter.on(Pattern.compile(positivelookAroundRegex))
+ .splitToList(text)).containsExactly("Hello", "@", "World", "@", "This", "@", "Is", "@", "A", "@", "Java", "@", "Program");
+
+ assertThat(Splitter.onPattern(positiveLookAroundMultiDelimiterRegex)
+ .splitToList(textMixed)).containsExactly("@", "HelloWorld", "@", "This", ":", "Is", "@", "A", "#", "Java", "#", "Program");
+
+ assertThat(Splitter.on(Pattern.compile(positiveLookAroundMultiDelimiterRegex))
+ .splitToList(textMixed)).containsExactly("@", "HelloWorld", "@", "This", ":", "Is", "@", "A", "#", "Java", "#", "Program");
+
+ }
+}
diff --git a/core-java-modules/core-java-strings/pom.xml b/core-java-modules/core-java-strings/pom.xml
index 137499de6b..aca0bb3346 100644
--- a/core-java-modules/core-java-strings/pom.xml
+++ b/core-java-modules/core-java-strings/pom.xml
@@ -57,6 +57,7 @@
3.6.1
61.1
+ 15
\ No newline at end of file
diff --git a/core-java-modules/core-java-strings/src/main/java/com/baeldung/multiline/MultiLineString.java b/core-java-modules/core-java-strings/src/main/java/com/baeldung/multiline/MultiLineString.java
index 987bc751cd..5d333799c4 100644
--- a/core-java-modules/core-java-strings/src/main/java/com/baeldung/multiline/MultiLineString.java
+++ b/core-java-modules/core-java-strings/src/main/java/com/baeldung/multiline/MultiLineString.java
@@ -64,4 +64,11 @@ public class MultiLineString {
return new String(Files.readAllBytes(Paths.get("src/main/resources/stephenking.txt")));
}
+ public String textBlocks() {
+ return """
+ Get busy living
+ or
+ get busy dying.
+ --Stephen King""";
+ }
}
diff --git a/core-java-modules/core-java-strings/src/test/java/com/baeldung/multiline/MultiLineStringUnitTest.java b/core-java-modules/core-java-strings/src/test/java/com/baeldung/multiline/MultiLineStringUnitTest.java
index 04d318c71b..66bc0a655b 100644
--- a/core-java-modules/core-java-strings/src/test/java/com/baeldung/multiline/MultiLineStringUnitTest.java
+++ b/core-java-modules/core-java-strings/src/test/java/com/baeldung/multiline/MultiLineStringUnitTest.java
@@ -16,6 +16,7 @@ public class MultiLineStringUnitTest {
assertEquals(ms.stringJoin(), ms.stringBuilder());
assertEquals(ms.stringBuilder(), ms.guavaJoiner());
assertEquals(ms.guavaJoiner(), ms.loadFromFile());
+ assertEquals(ms.loadFromFile(), ms.textBlocks());
}
}
diff --git a/core-java-modules/core-java/README.md b/core-java-modules/core-java/README.md
index b0e740e3b5..14857d5d87 100644
--- a/core-java-modules/core-java/README.md
+++ b/core-java-modules/core-java/README.md
@@ -11,3 +11,4 @@
- [What is the serialVersionUID?](http://www.baeldung.com/java-serial-version-uid)
- [A Guide to the ResourceBundle](http://www.baeldung.com/java-resourcebundle)
- [Merging java.util.Properties Objects](https://www.baeldung.com/java-merging-properties)
+- [Deserialization Vulnerabilities in Java](https://www.baeldung.com/java-deserialization-vulnerabilities)
diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml
index b4aae7949f..b801d44a08 100644
--- a/core-java-modules/pom.xml
+++ b/core-java-modules/pom.xml
@@ -86,6 +86,7 @@
core-java-lang-oop-methods
core-java-lang-oop-others
core-java-lang-operators
+ core-java-lang-operators-2
core-java-lang-syntax
core-java-lang-syntax-2
core-java-networking
@@ -111,7 +112,6 @@
core-java-string-operations
core-java-string-operations-2
core-java-string-operations-3
- core-java-strings
core-java-sun
core-java-regex
pre-jpms
diff --git a/gradle/.gitignore b/gradle/.gitignore
index da88288c09..01d29dfced 100644
--- a/gradle/.gitignore
+++ b/gradle/.gitignore
@@ -1 +1,7 @@
/.gradle/
+
+**/build/**
+/build/
+
+# exclude jar for gradle wrapper
+!**/gradle/wrapper/*.jar
diff --git a/gradle/gradle-cucumber/README.md b/gradle/gradle-cucumber/README.md
new file mode 100644
index 0000000000..a92593e959
--- /dev/null
+++ b/gradle/gradle-cucumber/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Using Cucumber with Gradle](https://www.baeldung.com/java-cucumber-gradle)
diff --git a/gradle/gradle-cucumber/build.gradle b/gradle/gradle-cucumber/build.gradle
new file mode 100644
index 0000000000..e643e680c3
--- /dev/null
+++ b/gradle/gradle-cucumber/build.gradle
@@ -0,0 +1,68 @@
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
+plugins {
+ id 'java'
+ id 'se.thinkcode.cucumber-runner' version '0.0.8'
+}
+
+ext {
+ junitVersion = '5.7.2'
+ cucumberVersion = '6.10.4'
+}
+
+group 'com.baeldung'
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+dependencies {
+ testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
+
+ testImplementation "io.cucumber:cucumber-java:${cucumberVersion}"
+
+ testImplementation "io.cucumber:cucumber-junit:${cucumberVersion}"
+ testImplementation "org.junit.vintage:junit-vintage-engine:${junitVersion}"
+}
+
+configurations {
+ cucumberRuntime {
+ extendsFrom testImplementation
+ }
+}
+
+task cucumberCli() {
+ dependsOn assemble, testClasses
+ doLast {
+ javaexec {
+ main = "io.cucumber.core.cli.Main"
+ classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
+ args = [
+ '--plugin', 'pretty',
+ '--plugin', 'html:target/cucumber-report.html',
+ '--glue', 'com.baeldung.cucumber',
+ 'src/test/resources']
+ }
+ }
+}
+
+cucumber {
+ main = 'io.cucumber.core.cli.Main'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED
+ }
+
+ systemProperties(project.gradle.startParameter.systemPropertiesArgs)
+}
diff --git a/gradle/gradle-cucumber/gradle/wrapper/gradle-wrapper.jar b/gradle/gradle-cucumber/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..e708b1c023
Binary files /dev/null and b/gradle/gradle-cucumber/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/gradle-cucumber/gradle/wrapper/gradle-wrapper.properties b/gradle/gradle-cucumber/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..da9702f9e7
--- /dev/null
+++ b/gradle/gradle-cucumber/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradle/gradle-cucumber/gradlew b/gradle/gradle-cucumber/gradlew
new file mode 100755
index 0000000000..4f906e0c81
--- /dev/null
+++ b/gradle/gradle-cucumber/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or 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 UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$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 "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# 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
+ ;;
+ 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradle/gradle-cucumber/gradlew.bat b/gradle/gradle-cucumber/gradlew.bat
new file mode 100644
index 0000000000..ac1b06f938
--- /dev/null
+++ b/gradle/gradle-cucumber/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/gradle-cucumber/settings.gradle b/gradle/gradle-cucumber/settings.gradle
new file mode 100644
index 0000000000..9b3cf0ebc7
--- /dev/null
+++ b/gradle/gradle-cucumber/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'gradle-cucumber'
diff --git a/gradle/gradle-cucumber/src/main/java/com/baeldung/Account.java b/gradle/gradle-cucumber/src/main/java/com/baeldung/Account.java
new file mode 100644
index 0000000000..48f1b031a2
--- /dev/null
+++ b/gradle/gradle-cucumber/src/main/java/com/baeldung/Account.java
@@ -0,0 +1,18 @@
+package com.baeldung;
+
+public class Account {
+
+ private Double balance;
+
+ public Account(Double initialBalance) {
+ this.balance = initialBalance;
+ }
+
+ public void credit(Double amount) {
+ balance += amount;
+ }
+
+ public Double getBalance() {
+ return balance;
+ }
+}
diff --git a/gradle/gradle-cucumber/src/test/java/com/baeldung/cucumber/RunCucumberTest.java b/gradle/gradle-cucumber/src/test/java/com/baeldung/cucumber/RunCucumberTest.java
new file mode 100644
index 0000000000..38c01f5487
--- /dev/null
+++ b/gradle/gradle-cucumber/src/test/java/com/baeldung/cucumber/RunCucumberTest.java
@@ -0,0 +1,14 @@
+package com.baeldung.cucumber;
+
+import io.cucumber.junit.Cucumber;
+import io.cucumber.junit.CucumberOptions;
+import org.junit.runner.RunWith;
+
+@RunWith(Cucumber.class)
+@CucumberOptions(
+ plugin = {"pretty", "html:target/cucumber-report.html"},
+ features = {"src/test/resources"}
+)
+public class RunCucumberTest {
+
+}
diff --git a/gradle/gradle-cucumber/src/test/java/com/baeldung/cucumber/StepDefinitions.java b/gradle/gradle-cucumber/src/test/java/com/baeldung/cucumber/StepDefinitions.java
new file mode 100644
index 0000000000..a3d0d7961b
--- /dev/null
+++ b/gradle/gradle-cucumber/src/test/java/com/baeldung/cucumber/StepDefinitions.java
@@ -0,0 +1,28 @@
+package com.baeldung.cucumber;
+
+import com.baeldung.Account;
+import io.cucumber.java.en.Given;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class StepDefinitions {
+
+ private Account account;
+
+ @Given("account balance is {double}")
+ public void givenAccountBalance(Double initialBalance) {
+ account = new Account(initialBalance);
+ }
+
+ @When("the account is credited with {double}")
+ public void whenAccountIsCredited(Double amount) {
+ account.credit(amount);
+ }
+
+ @Then("account should have a balance of {double}")
+ public void thenAccountShouldHaveBalance(Double expectedBalance) {
+ assertEquals(expectedBalance, account.getBalance());
+ }
+}
diff --git a/gradle/gradle-cucumber/src/test/resources/features/account_credited.feature b/gradle/gradle-cucumber/src/test/resources/features/account_credited.feature
new file mode 100644
index 0000000000..bd7940d1a5
--- /dev/null
+++ b/gradle/gradle-cucumber/src/test/resources/features/account_credited.feature
@@ -0,0 +1,6 @@
+Feature: Account is credited with amount
+
+ Scenario: Credit amount
+ Given account balance is 0.0
+ When the account is credited with 10.0
+ Then account should have a balance of 10.0
diff --git a/gradle/gradle-jacoco/build.gradle b/gradle/gradle-jacoco/build.gradle
new file mode 100644
index 0000000000..ef9e0a9c7c
--- /dev/null
+++ b/gradle/gradle-jacoco/build.gradle
@@ -0,0 +1,54 @@
+
+plugins {
+ id 'java'
+ id 'jacoco'
+}
+
+ext {
+ junitVersion = '5.7.2'
+ lombokVersion = '1.18.20'
+}
+
+group 'com.com.baeldung'
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+dependencies {
+ testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
+
+ compileOnly "org.projectlombok:lombok:${lombokVersion}"
+ annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
+}
+
+test {
+ useJUnitPlatform()
+
+ finalizedBy jacocoTestReport // report is always generated after tests run
+}
+
+jacocoTestReport {
+ dependsOn test // tests are required to run before generating the report
+
+ afterEvaluate {
+ classDirectories.setFrom(files(classDirectories.files.collect {
+ fileTree(dir: it, exclude: [
+ "com/baeldung/**/ExcludedPOJO.class",
+ "com/baeldung/**/*DTO.*",
+ "**/config/*"
+ ])
+ }))
+ }
+}
+
+jacoco {
+ toolVersion = "0.8.6"
+}
diff --git a/gradle/gradle-jacoco/gradle/wrapper/gradle-wrapper.jar b/gradle/gradle-jacoco/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..e708b1c023
Binary files /dev/null and b/gradle/gradle-jacoco/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/gradle-jacoco/gradle/wrapper/gradle-wrapper.properties b/gradle/gradle-jacoco/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..da9702f9e7
--- /dev/null
+++ b/gradle/gradle-jacoco/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradle/gradle-jacoco/gradlew b/gradle/gradle-jacoco/gradlew
new file mode 100755
index 0000000000..4f906e0c81
--- /dev/null
+++ b/gradle/gradle-jacoco/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or 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 UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$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 "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# 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
+ ;;
+ 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradle/gradle-jacoco/gradlew.bat b/gradle/gradle-jacoco/gradlew.bat
new file mode 100644
index 0000000000..ac1b06f938
--- /dev/null
+++ b/gradle/gradle-jacoco/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/gradle-jacoco/lombok.config b/gradle/gradle-jacoco/lombok.config
new file mode 100644
index 0000000000..7a21e88040
--- /dev/null
+++ b/gradle/gradle-jacoco/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
diff --git a/gradle/gradle-jacoco/settings.gradle b/gradle/gradle-jacoco/settings.gradle
new file mode 100644
index 0000000000..b0ed8f1486
--- /dev/null
+++ b/gradle/gradle-jacoco/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'gradle-jacoco'
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/config/AppConfig.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/config/AppConfig.java
new file mode 100644
index 0000000000..d103f4b4f5
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/config/AppConfig.java
@@ -0,0 +1,11 @@
+package com.baeldung.config;
+
+import com.baeldung.service.ProductService;
+
+public class AppConfig {
+
+ public ProductService productService() {
+ return new ProductService();
+ }
+
+}
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/domain/Product.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/domain/Product.java
new file mode 100644
index 0000000000..c64b6d2eae
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/domain/Product.java
@@ -0,0 +1,12 @@
+package com.baeldung.domain;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Builder
+@Data
+public class Product {
+ private int id;
+ private String name;
+
+}
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/dto/ExcludedPOJO.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/dto/ExcludedPOJO.java
new file mode 100644
index 0000000000..0f7278459e
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/dto/ExcludedPOJO.java
@@ -0,0 +1,4 @@
+package com.baeldung.dto;
+
+public class ExcludedPOJO {
+}
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/dto/ProductDTO.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/dto/ProductDTO.java
new file mode 100644
index 0000000000..0ae1659c14
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/dto/ProductDTO.java
@@ -0,0 +1,4 @@
+package com.baeldung.dto;
+
+public class ProductDTO {
+}
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/generated/Customer.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/generated/Customer.java
new file mode 100644
index 0000000000..e7bb837a5c
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/generated/Customer.java
@@ -0,0 +1,11 @@
+package com.baeldung.generated;
+
+@Generated
+public class Customer {
+ // everything in this class will be excluded from jacoco report because of @Generated
+
+ @Override
+ public String toString() {
+ return "Customer{}";
+ }
+}
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/generated/Generated.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/generated/Generated.java
new file mode 100644
index 0000000000..865df8ca8a
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/generated/Generated.java
@@ -0,0 +1,15 @@
+package com.baeldung.generated;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Documented
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
+public @interface Generated {
+}
\ No newline at end of file
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/service/CustomerService.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/service/CustomerService.java
new file mode 100644
index 0000000000..e6dbe8df5e
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/service/CustomerService.java
@@ -0,0 +1,16 @@
+package com.baeldung.service;
+
+import com.baeldung.generated.Generated;
+
+public class CustomerService {
+
+ //this method will be excluded from coverage due to @Generated.
+ @Generated
+ public String getProductId() {
+ return "An ID";
+ }
+
+ public String getCustomerName() {
+ return "some name";
+ }
+}
diff --git a/gradle/gradle-jacoco/src/main/java/com/baeldung/service/ProductService.java b/gradle/gradle-jacoco/src/main/java/com/baeldung/service/ProductService.java
new file mode 100644
index 0000000000..5f73ddc7fd
--- /dev/null
+++ b/gradle/gradle-jacoco/src/main/java/com/baeldung/service/ProductService.java
@@ -0,0 +1,9 @@
+package com.baeldung.service;
+
+public class ProductService {
+ private static final double DISCOUNT = 0.25;
+
+ public double getSalePrice(double originalPrice) {
+ return originalPrice - originalPrice * DISCOUNT;
+ }
+}
diff --git a/gradle/gradle-jacoco/src/test/java/com/baeldung/service/CustomerServiceUnitTest.java b/gradle/gradle-jacoco/src/test/java/com/baeldung/service/CustomerServiceUnitTest.java
new file mode 100644
index 0000000000..63dd2c755a
--- /dev/null
+++ b/gradle/gradle-jacoco/src/test/java/com/baeldung/service/CustomerServiceUnitTest.java
@@ -0,0 +1,14 @@
+package com.baeldung.service;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class CustomerServiceUnitTest {
+
+ @Test
+ public void givenCustomer_whenGetCustomer_thenReturnNewCustomer() {
+ CustomerService customerService = new CustomerService();
+ assertNotNull(customerService.getCustomerName());
+ }
+}
diff --git a/gradle/gradle-jacoco/src/test/java/com/baeldung/service/ProductServiceUnitTest.java b/gradle/gradle-jacoco/src/test/java/com/baeldung/service/ProductServiceUnitTest.java
new file mode 100644
index 0000000000..a9d216785a
--- /dev/null
+++ b/gradle/gradle-jacoco/src/test/java/com/baeldung/service/ProductServiceUnitTest.java
@@ -0,0 +1,15 @@
+package com.baeldung.service;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ProductServiceUnitTest {
+
+ @Test
+ public void givenOriginalPrice_whenGetSalePrice_thenReturnsDiscountedPrice() {
+ ProductService productService = new ProductService();
+ double salePrice = productService.getSalePrice(100);
+ assertEquals(salePrice, 75);
+ }
+}
diff --git a/gradle/gradle-jacoco/src/test/resources/features/account_credited.feature b/gradle/gradle-jacoco/src/test/resources/features/account_credited.feature
new file mode 100644
index 0000000000..bd7940d1a5
--- /dev/null
+++ b/gradle/gradle-jacoco/src/test/resources/features/account_credited.feature
@@ -0,0 +1,6 @@
+Feature: Account is credited with amount
+
+ Scenario: Credit amount
+ Given account balance is 0.0
+ When the account is credited with 10.0
+ Then account should have a balance of 10.0
diff --git a/jws/pom.xml b/jws/pom.xml
index be42798fd1..3d2f67c691 100644
--- a/jws/pom.xml
+++ b/jws/pom.xml
@@ -1,6 +1,7 @@
-
+
4.0.0
com.example
jws
@@ -66,4 +67,4 @@
3.0.2
-
+
\ No newline at end of file
diff --git a/kubernetes/k8s-intro/pom.xml b/kubernetes/k8s-intro/pom.xml
index 61722cb2c8..5da137ebb6 100644
--- a/kubernetes/k8s-intro/pom.xml
+++ b/kubernetes/k8s-intro/pom.xml
@@ -17,6 +17,7 @@
client-java
11.0.0
+
ch.qos.logback
logback-classic
diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/RunJob.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/RunJob.java
new file mode 100644
index 0000000000..0c73dfb203
--- /dev/null
+++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/RunJob.java
@@ -0,0 +1,152 @@
+/**
+ *
+ */
+package com.baeldung.kubernetes.intro;
+
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.kubernetes.client.custom.V1Patch;
+import io.kubernetes.client.custom.V1Patch.V1PatchAdapter;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.apis.BatchV1Api;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1DeleteOptions;
+import io.kubernetes.client.openapi.models.V1DeleteOptionsBuilder;
+import io.kubernetes.client.openapi.models.V1Job;
+import io.kubernetes.client.openapi.models.V1JobBuilder;
+import io.kubernetes.client.openapi.models.V1JobSpec;
+import io.kubernetes.client.openapi.models.V1JobSpecBuilder;
+import io.kubernetes.client.openapi.models.V1ObjectMeta;
+import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
+import io.kubernetes.client.openapi.models.V1Status;
+import io.kubernetes.client.util.Config;
+import io.kubernetes.client.util.PatchUtils;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+
+/**
+ * @author Philippe
+ *
+ */
+public class RunJob {
+
+ private static Logger log = LoggerFactory.getLogger(RunJob.class);
+
+ public static void main(String[] args) throws Exception {
+
+ // Create client with logginginterceptor
+ ApiClient client = Config.defaultClient();
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> log.info(message));
+ interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
+ OkHttpClient newClient = client.getHttpClient()
+ .newBuilder()
+ .addInterceptor(interceptor)
+ .readTimeout(0, TimeUnit.SECONDS)
+ .build();
+ client.setHttpClient(newClient);
+
+ // Create Job Spec
+ BatchV1Api api = new BatchV1Api(client);
+ String ns = "report-jobs";
+ V1Job body = new V1JobBuilder()
+ .withNewMetadata()
+ .withNamespace(ns)
+ .withName("payroll-report-job")
+ .endMetadata()
+ .withNewSpec()
+ .withCompletions(2)
+ .withParallelism(1)
+ .withNewTemplate()
+ .withNewMetadata()
+ .addToLabels("name", "payroll-report")
+ .endMetadata()
+ .editOrNewSpec()
+ .addNewContainer()
+ .withName("main")
+ .withImage("alpine")
+ .addNewCommand("/bin/sh")
+ .addNewArg("-c")
+ .addNewArg("sleep 10")
+ .endContainer()
+ .withRestartPolicy("Never")
+ .endSpec()
+ .endTemplate()
+ .endSpec()
+ .build();
+
+ // Send to K8S
+ V1Job createdJob = api.createNamespacedJob(ns, body, null, null, null);
+ log.info("job: uid={}", createdJob.getMetadata().getUid());
+
+ // Let's change its parallelism value
+ V1Job patchedJob = new V1JobBuilder(createdJob)
+ .withNewMetadata()
+ .withName(createdJob.getMetadata().getName())
+ .withNamespace(createdJob.getMetadata().getNamespace())
+ .endMetadata()
+ .editSpec()
+ .withParallelism(2)
+ .endSpec()
+ .build();
+
+ String patchedJobJSON = client.getJSON().serialize(patchedJob);
+ V1Patch patch = new V1Patch(patchedJobJSON);
+
+ PatchUtils.patch(
+ V1Job.class,
+ () -> api.patchNamespacedJobCall(
+ createdJob.getMetadata().getName(),
+ createdJob.getMetadata().getNamespace(),
+ patch,
+ null,
+ null,
+ "acme",
+ true,
+ null),
+ V1Patch.PATCH_FORMAT_APPLY_YAML,
+ api.getApiClient());
+
+ while(!jobCompleted(api,createdJob)) {
+ log.info("[I75] still running...");
+ Thread.sleep(1000);
+ }
+
+ V1Status response = api.deleteNamespacedJob(
+ createdJob.getMetadata().getName(),
+ createdJob.getMetadata().getNamespace(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null ) ;
+
+ log.info("[I122] response={}", response);
+ }
+
+ private static boolean jobCompleted(BatchV1Api api, V1Job createdJob) throws Exception {
+
+ V1Job job = api.readNamespacedJob(
+ createdJob.getMetadata().getName(),
+ createdJob.getMetadata().getNamespace(),
+ null,null,null);
+
+ if ( job.getStatus() == null ) {
+ return false;
+ }
+
+ log.info("[I88] Status: active={}, succeeded={}, failed={}",
+ job.getStatus().getActive(),
+ job.getStatus().getSucceeded(),
+ job.getStatus().getFailed()
+ );
+ Integer active = job.getStatus().getActive();
+
+ return active == null || active == 0 ;
+ }
+
+}
diff --git a/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/RunJobLiveTest.java b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/RunJobLiveTest.java
new file mode 100644
index 0000000000..d6621db2d4
--- /dev/null
+++ b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/RunJobLiveTest.java
@@ -0,0 +1,10 @@
+package com.baeldung.kubernetes.intro;
+
+import org.junit.jupiter.api.Test;
+
+class RunJobLiveTest {
+ @Test
+ void whenWatchPods_thenSuccess() throws Exception {
+ RunJob.main(new String[] {});
+ }
+}
diff --git a/libraries-4/pom.xml b/libraries-4/pom.xml
index decd467de9..756bfbd3a8 100644
--- a/libraries-4/pom.xml
+++ b/libraries-4/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
libraries-4
diff --git a/libraries-5/pom.xml b/libraries-5/pom.xml
index ff6c208f5f..a3ca204995 100644
--- a/libraries-5/pom.xml
+++ b/libraries-5/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
libraries-5
diff --git a/libraries-6/pom.xml b/libraries-6/pom.xml
index 6db3b1b77b..289597adc9 100644
--- a/libraries-6/pom.xml
+++ b/libraries-6/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
libraries-6
@@ -112,12 +112,12 @@
renjin-script-engine
${renjin.version}
-
+
com.googlecode.libphonenumber
libphonenumber
${libphonenumber.version}
-
+
diff --git a/libraries-data-3/README.md b/libraries-data-3/README.md
new file mode 100644
index 0000000000..fffdf65252
--- /dev/null
+++ b/libraries-data-3/README.md
@@ -0,0 +1,10 @@
+## Data Libraries
+
+This module contains articles about libraries for data processing in Java.
+
+### Relevant articles
+- [Kafka Streams vs Kafka Consumer]()
+- More articles: [[<-- prev]](/../libraries-data-2)
+
+##### Building the project
+You can build the project from the command line using: *mvn clean install*, or in an IDE. If you have issues with the derive4j imports in your IDE, you have to add the folder: *target/generated-sources/annotations* to the project build path in your IDE.
diff --git a/libraries-data-3/log4j.properties b/libraries-data-3/log4j.properties
new file mode 100644
index 0000000000..2173c5d96f
--- /dev/null
+++ b/libraries-data-3/log4j.properties
@@ -0,0 +1 @@
+log4j.rootLogger=INFO, stdout
diff --git a/libraries-data-3/pom.xml b/libraries-data-3/pom.xml
new file mode 100644
index 0000000000..bfc39e537e
--- /dev/null
+++ b/libraries-data-3/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+ libraries-data-3
+ libraries-data-3
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ org.apache.kafka
+ kafka-clients
+ ${kafka.version}
+ test
+ test
+
+
+ org.apache.kafka
+ kafka-streams
+ ${kafka.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+ ${slf4j.version}
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.testcontainers
+ kafka
+ ${testcontainers-kafka.version}
+ test
+
+
+
+
+ 3.6.2
+ 1.7.25
+ 2.8.0
+ 1.15.3
+
+
+
\ No newline at end of file
diff --git a/libraries-data-3/src/test/java/com/baeldung/kafka/streams/KafkaStreamsLiveTest.java b/libraries-data-3/src/test/java/com/baeldung/kafka/streams/KafkaStreamsLiveTest.java
new file mode 100644
index 0000000000..0d4c0606e3
--- /dev/null
+++ b/libraries-data-3/src/test/java/com/baeldung/kafka/streams/KafkaStreamsLiveTest.java
@@ -0,0 +1,279 @@
+package com.baeldung.kafka.streams;
+
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.apache.kafka.common.serialization.Serde;
+import org.apache.kafka.common.serialization.Serdes;
+import org.apache.kafka.common.utils.Bytes;
+import org.apache.kafka.streams.KafkaStreams;
+import org.apache.kafka.streams.KeyValue;
+import org.apache.kafka.streams.StoreQueryParameters;
+import org.apache.kafka.streams.StreamsBuilder;
+import org.apache.kafka.streams.StreamsConfig;
+import org.apache.kafka.streams.Topology;
+import org.apache.kafka.streams.kstream.Consumed;
+import org.apache.kafka.streams.kstream.Grouped;
+import org.apache.kafka.streams.kstream.JoinWindows;
+import org.apache.kafka.streams.kstream.KGroupedStream;
+import org.apache.kafka.streams.kstream.KGroupedTable;
+import org.apache.kafka.streams.kstream.KStream;
+import org.apache.kafka.streams.kstream.KTable;
+import org.apache.kafka.streams.kstream.Materialized;
+import org.apache.kafka.streams.kstream.Produced;
+import org.apache.kafka.streams.kstream.TimeWindows;
+import org.apache.kafka.streams.state.KeyValueIterator;
+import org.apache.kafka.streams.state.KeyValueStore;
+import org.apache.kafka.streams.state.QueryableStoreTypes;
+import org.apache.kafka.streams.state.ReadOnlyKeyValueStore;
+import org.apache.kafka.streams.state.StoreBuilder;
+import org.apache.kafka.streams.state.Stores;
+import org.apache.kafka.streams.state.WindowStore;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.testcontainers.containers.KafkaContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Properties;
+
+import static org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG;
+import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG;
+import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG;
+
+public class KafkaStreamsLiveTest {
+ private final String LEFT_TOPIC = "left-stream-topic";
+ private final String RIGHT_TOPIC = "right-stream-topic";
+ private final String LEFT_RIGHT_TOPIC = "left-right-stream-topic";
+
+ private KafkaProducer producer = createKafkaProducer();
+ private Properties streamsConfiguration = new Properties();
+
+ static final String TEXT_LINES_TOPIC = "TextLinesTopic";
+
+ private final String TEXT_EXAMPLE_1 = "test test and test";
+ private final String TEXT_EXAMPLE_2 = "test filter filter this sentence";
+
+ @ClassRule
+ public static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.4.3"));
+
+ @Before
+ public void setUp() {
+ streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());
+ streamsConfiguration.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
+ streamsConfiguration.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
+ streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 1000);
+ streamsConfiguration.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
+ }
+
+ @Test
+ public void shouldTestKafkaTableLatestWord() throws InterruptedException {
+ String inputTopic = "topicTable";
+
+ final StreamsBuilder builder = new StreamsBuilder();
+
+ KTable textLinesTable = builder.table(inputTopic,
+ Consumed.with(Serdes.String(), Serdes.String()));
+
+ textLinesTable.toStream().foreach((word, count) -> System.out.println("Latest word: " + word + " -> " + count));
+
+ final Topology topology = builder.build();
+ streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "latest-word-id");
+ KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration);
+
+ streams.cleanUp();
+ streams.start();
+ producer.send(new ProducerRecord(inputTopic, "1", TEXT_EXAMPLE_1));
+ producer.send(new ProducerRecord(inputTopic, "2", TEXT_EXAMPLE_2));
+
+ Thread.sleep(2000);
+ streams.close();
+ }
+
+ @Test
+ public void shouldTestWordCountKafkaStreams() throws InterruptedException {
+ String wordCountTopic = "wordCountTopic";
+
+ final StreamsBuilder builder = new StreamsBuilder();
+ KStream textLines = builder.stream(wordCountTopic,
+ Consumed.with(Serdes.String(), Serdes.String()));
+
+ KTable wordCounts = textLines
+ .flatMapValues(value -> Arrays.asList(value.toLowerCase(Locale.ROOT)
+ .split("\\W+")))
+ .groupBy((key, word) -> word)
+ .count(Materialized.> as("counts-store"));
+
+ wordCounts.toStream().foreach((word, count) -> System.out.println("Word: " + word + " -> " + count));
+
+ wordCounts.toStream().to("outputTopic",
+ Produced.with(Serdes.String(), Serdes.Long()));
+
+ streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-stream-table-id");
+ final Topology topology = builder.build();
+ KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration);
+
+ streams.cleanUp();
+ streams.start();
+
+ producer.send(new ProducerRecord(wordCountTopic, "1", TEXT_EXAMPLE_1));
+ producer.send(new ProducerRecord(wordCountTopic, "2", TEXT_EXAMPLE_2));
+
+ Thread.sleep(2000);
+ streams.close();
+ }
+
+ // Filter, map
+ @Test
+ public void shouldTestStatelessTransformations() throws InterruptedException {
+ String wordCountTopic = "wordCountTopic";
+
+ //when
+ final StreamsBuilder builder = new StreamsBuilder();
+ KStream textLines = builder.stream(wordCountTopic,
+ Consumed.with(Serdes.String(), Serdes.String()));
+
+ final KStream textLinesUpperCase =
+ textLines
+ .map((key, value) -> KeyValue.pair(value, value.toUpperCase()))
+ .filter((key, value) -> value.contains("FILTER"));
+
+ KTable wordCounts = textLinesUpperCase
+ .flatMapValues(value -> Arrays.asList(value.split("\\W+")))
+ .groupBy((key, word) -> word)
+ .count(Materialized.> as("counts-store"));
+
+ wordCounts.toStream().foreach((word, count) -> System.out.println("Word: " + word + " -> " + count));
+
+ streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-filter-map-id");
+ final Topology topology = builder.build();
+ KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration);
+
+ streams.cleanUp();
+ streams.start();
+
+ producer.send(new ProducerRecord(wordCountTopic, "1", TEXT_EXAMPLE_1));
+ producer.send(new ProducerRecord(wordCountTopic, "2", TEXT_EXAMPLE_2));
+
+ Thread.sleep(2000);
+ streams.close();
+
+ }
+
+ @Test
+ public void shouldTestAggregationStatefulTransformations() throws InterruptedException {
+ String aggregationTopic = "aggregationTopic";
+
+ final StreamsBuilder builder = new StreamsBuilder();
+ final KStream input = builder.stream(aggregationTopic,
+ Consumed.with(Serdes.ByteArray(), Serdes.String()));
+ final KTable aggregated = input
+ .groupBy((key, value) -> (value != null && value.length() > 0) ? value.substring(0, 2).toLowerCase() : "",
+ Grouped.with(Serdes.String(), Serdes.String()))
+ .aggregate(() -> 0L, (aggKey, newValue, aggValue) -> aggValue + newValue.length(),
+ Materialized.with(Serdes.String(), Serdes.Long()));
+
+ aggregated.toStream().foreach((word, count) -> System.out.println("Word: " + word + " -> " + count));
+
+ streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "aggregation-id");
+ final Topology topology = builder.build();
+ KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration);
+
+ streams.cleanUp();
+ streams.start();
+
+ producer.send(new ProducerRecord(aggregationTopic, "1", "one"));
+ producer.send(new ProducerRecord(aggregationTopic, "2", "two"));
+ producer.send(new ProducerRecord(aggregationTopic, "3", "three"));
+ producer.send(new ProducerRecord(aggregationTopic, "4", "four"));
+ producer.send(new ProducerRecord(aggregationTopic, "5", "five"));
+
+ Thread.sleep(5000);
+ streams.close();
+
+ }
+
+ @Test
+ public void shouldTestWindowingJoinStatefulTransformations() throws InterruptedException {
+ final StreamsBuilder builder = new StreamsBuilder();
+
+ KStream leftSource = builder.stream(LEFT_TOPIC);
+ KStream rightSource = builder.stream(RIGHT_TOPIC);
+
+ KStream leftRightSource = leftSource.outerJoin(rightSource,
+ (leftValue, rightValue) -> "left=" + leftValue + ", right=" + rightValue,
+ JoinWindows.of(Duration.ofSeconds(5)))
+ .groupByKey()
+ .reduce(((key, lastValue) -> lastValue))
+ .toStream();
+
+ leftRightSource.foreach((key, value) -> System.out.println("(key= " + key + ") -> (" + value + ")"));
+
+ final Topology topology = builder.build();
+ streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "windowing-join-id");
+ KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration);
+
+ streams.cleanUp();
+ streams.start();
+
+ producer.send(new ProducerRecord(LEFT_TOPIC, "1", "left"));
+ producer.send(new ProducerRecord(RIGHT_TOPIC, "2", "right"));
+
+ Thread.sleep(2000);
+ streams.close();
+ }
+
+ @Test
+ public void shouldTestWordCountWithInteractiveQueries() throws InterruptedException {
+
+ final Serde stringSerde = Serdes.String();
+ final StreamsBuilder builder = new StreamsBuilder();
+ final KStream
+ textLines = builder.stream(TEXT_LINES_TOPIC, Consumed.with(Serdes.String(), Serdes.String()));
+
+ final KGroupedStream groupedByWord = textLines
+ .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
+ .groupBy((key, word) -> word, Grouped.with(stringSerde, stringSerde));
+
+ groupedByWord.count(Materialized.>as("WordCountsStore")
+ .withValueSerde(Serdes.Long()));
+
+ streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-interactive-queries");
+
+ final KafkaStreams streams = new KafkaStreams(builder.build(), streamsConfiguration);
+ streams.cleanUp();
+ streams.start();
+
+ producer.send(new ProducerRecord(TEXT_LINES_TOPIC, "1", TEXT_EXAMPLE_1));
+ producer.send(new ProducerRecord(TEXT_LINES_TOPIC, "2", TEXT_EXAMPLE_2));
+
+ Thread.sleep(2000);
+ ReadOnlyKeyValueStore keyValueStore =
+ streams.store(StoreQueryParameters.fromNameAndType(
+ "WordCountsStore", QueryableStoreTypes.keyValueStore()));
+
+ KeyValueIterator range = keyValueStore.all();
+ while (range.hasNext()) {
+ KeyValue next = range.next();
+ System.out.println("Count for " + next.key + ": " + next.value);
+ }
+
+ streams.close();
+ }
+
+ private static KafkaProducer createKafkaProducer() {
+
+ Properties props = new Properties();
+ props.put(BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());
+ props.put(KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
+ props.put(VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
+
+ return new KafkaProducer(props);
+
+ }
+}
+
+
diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java
index f6c6673705..02f45e1282 100644
--- a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java
+++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java
@@ -21,7 +21,12 @@ public class ErrorResponseInterceptor implements Interceptor {
Gson gson = new Gson();
String body = gson.toJson(new ErrorMessage(response.code(), "The response from the server was not OK"));
ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON);
-
+
+ ResponseBody originalBody = response.body();
+ if (originalBody != null) {
+ originalBody.close();
+ }
+
return response.newBuilder()
.body(responseBody)
.build();
diff --git a/libraries/pom.xml b/libraries/pom.xml
index 13f91fddd0..40cc1b4671 100644
--- a/libraries/pom.xml
+++ b/libraries/pom.xml
@@ -1,6 +1,7 @@
-
+
4.0.0
libraries
libraries
@@ -335,7 +336,8 @@
benchmarks
-
+
org.openjdk.jmh.Main
diff --git a/logging-modules/log4j/src/main/java/com/baeldung/log4j/NoAppenderExample.java b/logging-modules/log4j/src/main/java/com/baeldung/log4j/NoAppenderExample.java
new file mode 100644
index 0000000000..9bd8a06537
--- /dev/null
+++ b/logging-modules/log4j/src/main/java/com/baeldung/log4j/NoAppenderExample.java
@@ -0,0 +1,18 @@
+package com.baeldung.log4j;
+
+import org.apache.log4j.Logger;
+
+public class NoAppenderExample {
+
+ private final static Logger logger = Logger.getLogger(NoAppenderExample.class);
+
+ public static void main(String[] args) {
+
+ //Setup default appender
+ //BasicConfigurator.configure();
+
+ //Define path to configuration file
+ //PropertyConfigurator.configure("src\\main\\resources\\log4j.properties");
+ logger.info("Info log message");
+ }
+}
diff --git a/logging-modules/log4j/src/main/resources/log4j.properties b/logging-modules/log4j/src/main/resources/log4j.properties
new file mode 100644
index 0000000000..b10ba2b7d4
--- /dev/null
+++ b/logging-modules/log4j/src/main/resources/log4j.properties
@@ -0,0 +1,5 @@
+log4j.rootLogger=INFO, stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n
\ No newline at end of file
diff --git a/logging-modules/log4j/src/main/resources/log4j.xml b/logging-modules/log4j/src/main/resources/log4j.xml
index 562d6920f9..3004649edf 100644
--- a/logging-modules/log4j/src/main/resources/log4j.xml
+++ b/logging-modules/log4j/src/main/resources/log4j.xml
@@ -90,6 +90,8 @@
+
+
diff --git a/lombok/pom.xml b/lombok/pom.xml
index 334d1defc9..c5758ea8df 100644
--- a/lombok/pom.xml
+++ b/lombok/pom.xml
@@ -73,8 +73,6 @@
-
- 1.18.10
1.0.0.Final
diff --git a/mapstruct/src/main/java/com/baeldung/mapper/SimpleDestinationMapperUsingInjectedService.java b/mapstruct/src/main/java/com/baeldung/mapper/SimpleDestinationMapperUsingInjectedService.java
new file mode 100644
index 0000000000..22e6499711
--- /dev/null
+++ b/mapstruct/src/main/java/com/baeldung/mapper/SimpleDestinationMapperUsingInjectedService.java
@@ -0,0 +1,22 @@
+package com.baeldung.mapper;
+
+import com.baeldung.dto.SimpleSource;
+import com.baeldung.entity.SimpleDestination;
+import com.baeldung.service.SimpleService;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Mapper(componentModel = "spring")
+public abstract class SimpleDestinationMapperUsingInjectedService {
+
+ @Autowired
+ protected SimpleService simpleService;
+
+ @Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")
+ public abstract SimpleDestination sourceToDestination(SimpleSource source);
+
+ public abstract SimpleSource destinationToSource(SimpleDestination destination);
+
+
+}
diff --git a/mapstruct/src/main/java/com/baeldung/service/SimpleService.java b/mapstruct/src/main/java/com/baeldung/service/SimpleService.java
new file mode 100644
index 0000000000..14b6c09592
--- /dev/null
+++ b/mapstruct/src/main/java/com/baeldung/service/SimpleService.java
@@ -0,0 +1,11 @@
+package com.baeldung.service;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class SimpleService {
+
+ public String enrichName(String name) {
+ return "-:: " + name + " ::-";
+ }
+}
diff --git a/mapstruct/src/test/java/com/baeldung/mapper/SimpleDestinationMapperUsingInjectedIntegrationTest.java b/mapstruct/src/test/java/com/baeldung/mapper/SimpleDestinationMapperUsingInjectedIntegrationTest.java
new file mode 100644
index 0000000000..3bfbc60de6
--- /dev/null
+++ b/mapstruct/src/test/java/com/baeldung/mapper/SimpleDestinationMapperUsingInjectedIntegrationTest.java
@@ -0,0 +1,35 @@
+package com.baeldung.mapper;
+
+import com.baeldung.dto.SimpleSource;
+import com.baeldung.entity.SimpleDestination;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SimpleDestinationMapperUsingInjectedIntegrationTest {
+
+ @Autowired
+ private SimpleDestinationMapperUsingInjectedService mapper;
+
+ @Test
+ public void givenSourceToDestination_whenMaps_thenNameEnriched() {
+ // Given
+ SimpleSource source = new SimpleSource();
+ source.setName("Bob");
+ source.setDescription("The Builder");
+
+ // When
+ SimpleDestination destination = mapper.sourceToDestination(source);
+
+ // Then
+ assertThat(destination).isNotNull();
+ assertThat(destination.getName()).isEqualTo("-:: Bob ::-");
+ assertThat(destination.getDescription()).isEqualTo("The Builder");
+ }
+}
\ No newline at end of file
diff --git a/maven-modules/maven-copy-files/README.md b/maven-modules/maven-copy-files/README.md
new file mode 100644
index 0000000000..1e3a75cb0b
--- /dev/null
+++ b/maven-modules/maven-copy-files/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Copying Files With Maven](https://www.baeldung.com/maven-copy-files)
diff --git a/maven-modules/maven-pom-types/README.md b/maven-modules/maven-pom-types/README.md
new file mode 100644
index 0000000000..40119f68c1
--- /dev/null
+++ b/maven-modules/maven-pom-types/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Difference Between Super, Simplest, and Effective POM](https://www.baeldung.com/maven-super-simplest-effective-pom)
diff --git a/maven-modules/maven-pom-types/effective-pom.xml b/maven-modules/maven-pom-types/effective-pom.xml
new file mode 100644
index 0000000000..a2f8460809
--- /dev/null
+++ b/maven-modules/maven-pom-types/effective-pom.xml
@@ -0,0 +1,238 @@
+
+
+ 4.0.0
+ com.baeldung
+ maven-pom-types
+ 1.0-SNAPSHOT
+
+
+
+ false
+
+ central
+ Central Repository
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+ never
+
+
+ false
+
+ central
+ Central Repository
+ https://repo.maven.apache.org/maven2
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\src\main\java
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\src\main\scripts
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\src\test\java
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget\classes
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget\test-classes
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\src\main\resources
+
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\src\test\resources
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget
+ simplestPOM-1.0-SNAPSHOT
+
+
+
+ maven-antrun-plugin
+ 1.3
+
+
+ maven-assembly-plugin
+ 2.2-beta-5
+
+
+ maven-dependency-plugin
+ 2.8
+
+
+ maven-release-plugin
+ 2.5.3
+
+
+
+
+
+ maven-clean-plugin
+ 2.5
+
+
+ default-clean
+ clean
+
+ clean
+
+
+
+
+
+ maven-resources-plugin
+ 2.6
+
+
+ default-testResources
+ process-test-resources
+
+ testResources
+
+
+
+ default-resources
+ process-resources
+
+ resources
+
+
+
+
+
+ maven-jar-plugin
+ 2.4
+
+
+ default-jar
+ package
+
+ jar
+
+
+
+
+
+ maven-compiler-plugin
+ 3.1
+
+
+ default-compile
+ compile
+
+ compile
+
+
+
+ default-testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ maven-surefire-plugin
+ 2.12.4
+
+
+ default-test
+ test
+
+ test
+
+
+
+
+
+ maven-install-plugin
+ 2.4
+
+
+ default-install
+ install
+
+ install
+
+
+
+
+
+ maven-deploy-plugin
+ 2.7
+
+
+ default-deploy
+ deploy
+
+ deploy
+
+
+
+
+
+ maven-site-plugin
+ 3.3
+
+
+ default-site
+ site
+
+ site
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget\site
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+
+
+
+
+
+ default-deploy
+ site-deploy
+
+ deploy
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget\site
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+
+
+
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget\site
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+
+
+
+
+
+
+
+ C:\Users\emicu\Desktop\tutorials\maven-modules\maven-pom-types\customTarget\site
+
+
+
diff --git a/maven-modules/maven-pom-types/pom-4.0.0.xml b/maven-modules/maven-pom-types/pom-4.0.0.xml
new file mode 100644
index 0000000000..24b15984af
--- /dev/null
+++ b/maven-modules/maven-pom-types/pom-4.0.0.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+ 4.0.0
+
+
+
+ central
+ Central Repository
+ https://repo.maven.apache.org/maven2
+ default
+
+ false
+
+
+
+
+
+
+ central
+ Central Repository
+ https://repo.maven.apache.org/maven2
+ default
+
+ false
+
+
+ never
+
+
+
+
+
+ ${project.basedir}/target
+ ${project.build.directory}/classes
+ ${project.artifactId}-${project.version}
+ ${project.build.directory}/test-classes
+
+ ${project.basedir}/src/main/java
+ ${project.basedir}/src/main/scripts
+
+ ${project.basedir}/src/test/java
+
+
+
+ ${project.basedir}/src/main/resources
+
+
+
+
+ ${project.basedir}/src/test/resources
+
+
+
+
+
+
+
+ maven-antrun-plugin
+ 1.3
+
+
+ maven-assembly-plugin
+ 2.2-beta-5
+
+
+ maven-dependency-plugin
+ 2.8
+
+
+ maven-release-plugin
+ 2.5.3
+
+
+
+
+
+
+ ${project.build.directory}/site
+
+
+
+
+
+ release-profile
+
+
+
+ performRelease
+ true
+
+
+
+
+
+
+ true
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ true
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ true
+ maven-deploy-plugin
+
+ true
+
+
+
+
+
+
+
+
+
diff --git a/maven-modules/maven-pom-types/pom.xml b/maven-modules/maven-pom-types/pom.xml
new file mode 100644
index 0000000000..98fbc828a0
--- /dev/null
+++ b/maven-modules/maven-pom-types/pom.xml
@@ -0,0 +1,9 @@
+
+
+ 4.0.0
+ com.baeldung
+ maven-pom-types
+ 1.0-SNAPSHOT
+
\ No newline at end of file
diff --git a/maven-modules/maven-printing-plugins/README.md b/maven-modules/maven-printing-plugins/README.md
new file mode 100644
index 0000000000..862c4bcdd1
--- /dev/null
+++ b/maven-modules/maven-printing-plugins/README.md
@@ -0,0 +1,7 @@
+## Maven Printing Plugins
+
+This module contains articles about printing from Maven plugins.
+
+### Relevant Articles
+
+- [How to Display a Message in Maven](https://www.baeldung.com/maven-print-message-during-execution)
diff --git a/maven-modules/maven-printing-plugins/pom.xml b/maven-modules/maven-printing-plugins/pom.xml
index 6ea1ab2a84..805c3c1633 100644
--- a/maven-modules/maven-printing-plugins/pom.xml
+++ b/maven-modules/maven-printing-plugins/pom.xml
@@ -49,9 +49,11 @@
echo
- Hello, world
- Embed a line break: ${line.separator}
- ArtifactId is ${project.artifactId}
+
+ Hello, world
+ Embed a line break: ${line.separator}
+ ArtifactId is ${project.artifactId}
+
INFO
/logs/log-echo.txt
true
diff --git a/netflix-modules/mantis/pom.xml b/netflix-modules/mantis/pom.xml
index 474b8f6dbf..1f8b377b94 100644
--- a/netflix-modules/mantis/pom.xml
+++ b/netflix-modules/mantis/pom.xml
@@ -19,7 +19,6 @@
org.springframework.boot
spring-boot-starter
- 2.1.3.RELEASE
io.mantisrx
@@ -35,36 +34,31 @@
com.fasterxml.jackson.core
jackson-databind
- 2.10.2
net.andreinc.mockneat
mockneat
- 0.3.8
+ 0.4.2
org.projectlombok
lombok
- 1.18.12
org.springframework
spring-webflux
- 5.0.9.RELEASE
test
io.projectreactor.netty
reactor-netty
- 0.9.12.RELEASE
test
-
- SpringLibReleaseRepo
- https://repo.spring.io/libs-release/
+ jcenter
+ https://jcenter.bintray.com/
diff --git a/parent-spring-5/pom.xml b/parent-spring-5/pom.xml
index b70ca2bd0f..6bedf9fb67 100644
--- a/parent-spring-5/pom.xml
+++ b/parent-spring-5/pom.xml
@@ -30,7 +30,7 @@
- 5.3.3
+ 5.3.7
5.2.3.RELEASE
1.5.10.RELEASE
diff --git a/patterns/.gitignore b/patterns/.gitignore
new file mode 100644
index 0000000000..1d420f6aaf
--- /dev/null
+++ b/patterns/.gitignore
@@ -0,0 +1 @@
+/product-service/
diff --git a/patterns/design-patterns-behavioral/pom.xml b/patterns/design-patterns-behavioral/pom.xml
index 93e07bb477..bc032a0f8f 100644
--- a/patterns/design-patterns-behavioral/pom.xml
+++ b/patterns/design-patterns-behavioral/pom.xml
@@ -26,6 +26,11 @@
${lombok.version}
provided
+
+ com.google.code.findbugs
+ annotations
+ ${findbugs.annotations.version}
+
org.apache.commons
commons-lang3
@@ -41,6 +46,7 @@
16.0.2
+ 3.0.1
3.9.1
diff --git a/patterns/design-patterns-behavioral/src/main/java/com/baeldung/nulls/FindBugsAnnotations.java b/patterns/design-patterns-behavioral/src/main/java/com/baeldung/nulls/FindBugsAnnotations.java
index 697d5e4959..594516e3f2 100644
--- a/patterns/design-patterns-behavioral/src/main/java/com/baeldung/nulls/FindBugsAnnotations.java
+++ b/patterns/design-patterns-behavioral/src/main/java/com/baeldung/nulls/FindBugsAnnotations.java
@@ -1,12 +1,12 @@
package com.baeldung.nulls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
public class FindBugsAnnotations {
- public void accept(@NotNull Object param) {
+ public void accept(@NonNull Object param) {
System.out.println(param.toString());
}
@@ -14,7 +14,7 @@ public class FindBugsAnnotations {
System.out.println("Printing " + param);
}
- @NotNull
+ @NonNull
public Object process() throws Exception {
Object result = doSomething();
if (result == null) {
diff --git a/patterns/enterprise-patterns/pom.xml b/patterns/enterprise-patterns/pom.xml
new file mode 100644
index 0000000000..3637072c58
--- /dev/null
+++ b/patterns/enterprise-patterns/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ patterns
+ 1.0.0-SNAPSHOT
+
+
+ enterprise-patterns
+ pom
+
+
+ 3.7.4
+
+
+
+
+ org.apache.camel.springboot
+ camel-spring-boot-starter
+
+
+ org.apache.camel.springboot
+ camel-activemq-starter
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.2.2.RELEASE
+
+
+ org.apache.camel
+ camel-test-spring-junit5
+ test
+
+
+
+
+
+
+ org.apache.camel.springboot
+ camel-spring-boot-dependencies
+ ${camel.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/patterns/enterprise-patterns/wire-tap/README.md b/patterns/enterprise-patterns/wire-tap/README.md
new file mode 100644
index 0000000000..5f2aaf0d5a
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/README.md
@@ -0,0 +1,34 @@
+# Wire Tap Pattern
+
+The application shows you how to use a Wire Tap to monitor, debug or troubleshoot messages flowing through the system, without permanently consuming them off, or making any changes to the expected message in the output channel.
+
+This example shows how to implement this with a simple Apache Camel application using Spring Boot and Apache ActiveMq.
+For convenience, we are using in-memory activeMq.
+
+
+
+### Configuring and using the Connection Factory
+
+1. Create CamelContext.
+2. Connect to embedded (or remote) ActiveMQ JMS broker.
+3. Add JMS queue to CamelContext.
+4. Load file orders (xml/csv) from src/data into the JMS queue.
+5. Based on the extension of the incoming file message, route to the respective queues.
+6. Test that the destination route is working.
+7. Audit the received file (order) from the wire tap queue.
+
+### How to run the example:
+
+ mvn spring-boot:run
+
+
+The Wire Tap processor, by default, makes a shallow copy of the Camel Exchange instance. The copy of the exchange is sent to the endpoint specified in the wireTap statement. The body of the wire tapped message contains the same object as that in the original message which means any change to the internal state of that object during the wire tap route may also end up changing the main message’s body.
+
+To solve this, we need to create a deep copy of the object before passing it to the wire tap destination. Wire Tap EIP provides us with a mechanism to perform a “deep” copy of the message, by implementing the org.apache.camel.Processor class. This needs to be be called using onPrepare statement right after wireTap.
+For more details, check out the AmqApplicationUnitTest.class.
+
+### Relevant Articles:
+
+- [Wire tap (Enterprise Integration Pattern)](https://drafts.baeldung.com/?p=103346&preview=true)
+- [Intro to Apache camel](https://www.baeldung.com/apache-camel-intro)
+
diff --git a/patterns/enterprise-patterns/wire-tap/pom.xml b/patterns/enterprise-patterns/wire-tap/pom.xml
new file mode 100644
index 0000000000..9169c4ac91
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+ wire-tap
+ 1.0
+ jar
+
+
+ enterprise-patterns
+ com.baeldung
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/patterns/enterprise-patterns/wire-tap/src/data/.camel/msg1.xml b/patterns/enterprise-patterns/wire-tap/src/data/.camel/msg1.xml
new file mode 100644
index 0000000000..0a6fa64552
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/data/.camel/msg1.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/src/data/msg.xml b/patterns/enterprise-patterns/wire-tap/src/data/msg.xml
new file mode 100644
index 0000000000..0a6fa64552
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/data/msg.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/AmqApplication.java b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/AmqApplication.java
new file mode 100644
index 0000000000..eacef86d75
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/AmqApplication.java
@@ -0,0 +1,70 @@
+package com.baeldung;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.jms.JmsComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AmqApplication {
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(AmqApplication.class, args);
+
+ try (CamelContext context = new DefaultCamelContext()) {
+ ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
+ "vm://localhost?broker.persistent=false");
+ connectionFactory.setTrustAllPackages(true);
+ context.addComponent("direct", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
+ addRoute(context);
+
+ try (ProducerTemplate template = context.createProducerTemplate()) {
+ context.start();
+
+ MyPayload payload = new MyPayload("One");
+ template.sendBody("direct:source", payload);
+ Thread.sleep(10000);
+ } finally {
+ context.stop();
+ }
+ }
+ }
+
+ private static void addRoute(CamelContext context) throws Exception {
+ context.addRoutes(newExchangeRoute());
+ }
+
+ static RoutesBuilder traditionalWireTapRoute() {
+ return new RouteBuilder() {
+ public void configure() {
+
+ from("direct:source").log("Main route: Send '${body}' to tap router").wireTap("direct:tap").delay(1000)
+ .log("Main route: Add 'two' to '${body}'").bean(MyBean.class, "addTwo").to("direct:destination")
+ .log("Main route: Output '${body}'");
+
+ from("direct:tap").log("Tap Wire route: received '${body}'")
+ .log("Tap Wire route: Add 'three' to '${body}'").bean(MyBean.class, "addThree")
+ .log("Tap Wire route: Output '${body}'");
+
+ from("direct:destination").log("Output at destination: '${body}'");
+ }
+ };
+ }
+
+ static RoutesBuilder newExchangeRoute() throws Exception {
+ return new RouteBuilder() {
+ public void configure() throws Exception {
+
+ from("direct:source").wireTap("direct:tap").onPrepare(new MyPayloadClonePrepare()).end().delay(1000);
+
+ from("direct:tap").bean(MyBean.class, "addThree");
+ }
+ };
+ }
+
+}
diff --git a/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyBean.java b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyBean.java
new file mode 100644
index 0000000000..62c45725b0
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyBean.java
@@ -0,0 +1,14 @@
+package com.baeldung;
+
+public class MyBean {
+
+ public MyPayload addTwo(MyPayload body) {
+ body.setValue(body.getValue() + " and two");
+ return body;
+ }
+
+ public MyPayload addThree(MyPayload body) {
+ body.setValue(body.getValue() + " and three");
+ return body;
+ }
+}
diff --git a/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyPayload.java b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyPayload.java
new file mode 100644
index 0000000000..84d6a22733
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyPayload.java
@@ -0,0 +1,31 @@
+package com.baeldung;
+
+import java.io.Serializable;
+
+public class MyPayload implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private String value;
+
+ public MyPayload(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ public MyPayload deepClone() {
+ MyPayload myPayload = new MyPayload(value);
+ return myPayload;
+ }
+
+}
diff --git a/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyPayloadClonePrepare.java b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyPayloadClonePrepare.java
new file mode 100644
index 0000000000..938194bc1a
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/main/java/com/baeldung/MyPayloadClonePrepare.java
@@ -0,0 +1,15 @@
+package com.baeldung;
+
+import java.util.Date;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+
+public class MyPayloadClonePrepare implements Processor {
+
+ public void process(Exchange exchange) throws Exception {
+ MyPayload myPayload = exchange.getIn().getBody(MyPayload.class);
+ exchange.getIn().setBody(myPayload.deepClone());
+ exchange.getIn().setHeader("date", new Date());
+ }
+}
diff --git a/patterns/enterprise-patterns/wire-tap/src/main/resources/application.properties b/patterns/enterprise-patterns/wire-tap/src/main/resources/application.properties
new file mode 100644
index 0000000000..a74f494fa5
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/main/resources/application.properties
@@ -0,0 +1,9 @@
+# to keep the JVM running
+camel.springboot.main-run-controller = true
+
+#configure the URL of the remote ActiveMQ broker
+#camel.component.activemq.broker-url=tcp://localhost:61616
+#spring.activemq.broker-url=tcp://localhost:61616
+
+spring.activemq.in-memory=true
+spring.activemq.pool.enabled=false
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/src/main/resources/log4j.properties b/patterns/enterprise-patterns/wire-tap/src/main/resources/log4j.properties
new file mode 100644
index 0000000000..28aa51259a
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/src/main/resources/log4j.properties
@@ -0,0 +1,16 @@
+# Root logger option
+log4j.rootLogger=INFO, file, console
+
+log4j.logger.com.javarticles=INFO, file
+
+# Direct log messages to a log file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.File=javarticles.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d | %p | %F %L | %m%n
+
+# Direct log messages to stdout
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Target=System.out
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%d{HH:mm}| %p | %F %L | %m%n
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-10-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-10-1-1-1
new file mode 100644
index 0000000000..25b70e3283
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-10-1-1-1
@@ -0,0 +1 @@
+Test Message: 8
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-11-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-11-1-1-1
new file mode 100644
index 0000000000..3948af158c
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-11-1-1-1
@@ -0,0 +1 @@
+Test Message: 9
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-2-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-2-1-1-1
new file mode 100644
index 0000000000..262fbf6de5
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-2-1-1-1
@@ -0,0 +1 @@
+Test Message: 0
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-3-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-3-1-1-1
new file mode 100644
index 0000000000..64a5c33134
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-3-1-1-1
@@ -0,0 +1 @@
+Test Message: 1
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-4-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-4-1-1-1
new file mode 100644
index 0000000000..de22e70d38
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-4-1-1-1
@@ -0,0 +1 @@
+Test Message: 2
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-5-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-5-1-1-1
new file mode 100644
index 0000000000..d400e1afeb
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-5-1-1-1
@@ -0,0 +1 @@
+Test Message: 3
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-6-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-6-1-1-1
new file mode 100644
index 0000000000..660c960fcb
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-6-1-1-1
@@ -0,0 +1 @@
+Test Message: 4
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-7-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-7-1-1-1
new file mode 100644
index 0000000000..c97f21578a
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-7-1-1-1
@@ -0,0 +1 @@
+Test Message: 5
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-8-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-8-1-1-1
new file mode 100644
index 0000000000..be38d662ff
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-8-1-1-1
@@ -0,0 +1 @@
+Test Message: 6
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-9-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-9-1-1-1
new file mode 100644
index 0000000000..518e4227fc
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-9-1-1-1
@@ -0,0 +1 @@
+Test Message: 7
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-10-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-10-1-1-1
new file mode 100644
index 0000000000..25b70e3283
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-10-1-1-1
@@ -0,0 +1 @@
+Test Message: 8
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-11-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-11-1-1-1
new file mode 100644
index 0000000000..3948af158c
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-11-1-1-1
@@ -0,0 +1 @@
+Test Message: 9
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-2-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-2-1-1-1
new file mode 100644
index 0000000000..262fbf6de5
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-2-1-1-1
@@ -0,0 +1 @@
+Test Message: 0
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-3-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-3-1-1-1
new file mode 100644
index 0000000000..64a5c33134
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-3-1-1-1
@@ -0,0 +1 @@
+Test Message: 1
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-4-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-4-1-1-1
new file mode 100644
index 0000000000..de22e70d38
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-4-1-1-1
@@ -0,0 +1 @@
+Test Message: 2
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-5-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-5-1-1-1
new file mode 100644
index 0000000000..d400e1afeb
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-5-1-1-1
@@ -0,0 +1 @@
+Test Message: 3
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-6-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-6-1-1-1
new file mode 100644
index 0000000000..660c960fcb
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-6-1-1-1
@@ -0,0 +1 @@
+Test Message: 4
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-7-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-7-1-1-1
new file mode 100644
index 0000000000..c97f21578a
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-7-1-1-1
@@ -0,0 +1 @@
+Test Message: 5
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-8-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-8-1-1-1
new file mode 100644
index 0000000000..be38d662ff
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-8-1-1-1
@@ -0,0 +1 @@
+Test Message: 6
\ No newline at end of file
diff --git a/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-9-1-1-1 b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-9-1-1-1
new file mode 100644
index 0000000000..518e4227fc
--- /dev/null
+++ b/patterns/enterprise-patterns/wire-tap/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-9-1-1-1
@@ -0,0 +1 @@
+Test Message: 7
\ No newline at end of file
diff --git a/patterns/pom.xml b/patterns/pom.xml
index 3bde26cae2..6e92ad2813 100644
--- a/patterns/pom.xml
+++ b/patterns/pom.xml
@@ -28,6 +28,7 @@
intercepting-filter
solid
clean-architecture
+ enterprise-patterns
diff --git a/persistence-modules/java-jpa-3/README.md b/persistence-modules/java-jpa-3/README.md
index 9c9e040825..c024d7c540 100644
--- a/persistence-modules/java-jpa-3/README.md
+++ b/persistence-modules/java-jpa-3/README.md
@@ -10,3 +10,6 @@ This module contains articles about the Java Persistence API (JPA) in Java.
- [JPA CascadeType.REMOVE vs orphanRemoval](https://www.baeldung.com/jpa-cascade-remove-vs-orphanremoval)
- [A Guide to MultipleBagFetchException in Hibernate](https://www.baeldung.com/java-hibernate-multiplebagfetchexception)
- [How to Convert a Hibernate Proxy to a Real Entity Object](https://www.baeldung.com/hibernate-proxy-to-real-entity-object)
+- [Returning an Auto-Generated Id with JPA](https://www.baeldung.com/jpa-get-auto-generated-id)
+- [How to Return Multiple Entities In JPA Query](https://www.baeldung.com/jpa-return-multiple-entities)
+- [Defining Unique Constraints in JPA](https://www.baeldung.com/jpa-unique-constraints)
diff --git a/persistence-modules/java-jpa-3/pom.xml b/persistence-modules/java-jpa-3/pom.xml
index 11673935b1..d81bfca5bd 100644
--- a/persistence-modules/java-jpa-3/pom.xml
+++ b/persistence-modules/java-jpa-3/pom.xml
@@ -68,6 +68,12 @@
${assertj.version}
test
+
+ junit
+ junit
+ ${junit.version}
+ test
+
diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/IdGeneration/User.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/IdGeneration/User.java
new file mode 100644
index 0000000000..88e742adce
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/IdGeneration/User.java
@@ -0,0 +1,41 @@
+package com.baeldung.jpa.IdGeneration;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class User {
+
+ @Id
+// @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long id;
+ private String username;
+ private String password;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/IdGeneration/UserService.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/IdGeneration/UserService.java
new file mode 100644
index 0000000000..9c34ef9bb4
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/IdGeneration/UserService.java
@@ -0,0 +1,19 @@
+package com.baeldung.jpa.IdGeneration;
+
+import javax.persistence.EntityManager;
+import javax.transaction.Transactional;
+
+public class UserService {
+
+ EntityManager entityManager;
+
+ public UserService(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Transactional
+ public long saveUser(User user){
+ entityManager.persist(user);
+ return user.getId();
+ }
+}
diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/returnmultipleentities/Channel.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/returnmultipleentities/Channel.java
new file mode 100644
index 0000000000..7f60dd8de4
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/returnmultipleentities/Channel.java
@@ -0,0 +1,43 @@
+package com.baeldung.jpa.returnmultipleentities;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import java.util.Objects;
+
+@Entity
+public class Channel {
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ private String code;
+
+ private Long subscriptionId;
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setSubscriptionId(Long subscriptionId) {
+ this.subscriptionId = subscriptionId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Channel channel = (Channel) o;
+ return Objects.equals(id, channel.id) && Objects.equals(code, channel.code) && Objects.equals(subscriptionId, channel.subscriptionId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, code, subscriptionId);
+ }
+}
diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/returnmultipleentities/ReportRepository.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/returnmultipleentities/ReportRepository.java
new file mode 100644
index 0000000000..ae574de7d5
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/returnmultipleentities/ReportRepository.java
@@ -0,0 +1,23 @@
+package com.baeldung.jpa.returnmultipleentities;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.persistence.Query;
+import java.util.List;
+
+public class ReportRepository {
+ private final EntityManagerFactory emf;
+
+ public ReportRepository() {
+ emf = Persistence.createEntityManagerFactory("jpa-h2-return-multiple-entities");
+ }
+
+ public List
+
+
+ org.hibernate.jpa.HibernatePersistenceProvider
+ com.baeldung.jpa.IdGeneration.User
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ org.hibernate.jpa.HibernatePersistenceProvider
+ com.baeldung.jpa.uniqueconstraints.Person
+ com.baeldung.jpa.uniqueconstraints.Address
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ org.hibernate.jpa.HibernatePersistenceProvider
+ com.baeldung.jpa.returnmultipleentities.Channel
+ com.baeldung.jpa.returnmultipleentities.Subscription
+ com.baeldung.jpa.returnmultipleentities.User
+ com.baeldung.jpa.returnmultipleentities.ReportRepository
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/IdGeneration/IdGenerationIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/IdGeneration/IdGenerationIntegrationTest.java
new file mode 100644
index 0000000000..941ad52344
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/IdGeneration/IdGenerationIntegrationTest.java
@@ -0,0 +1,47 @@
+package com.baeldung.jpa.IdGeneration;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import java.util.UUID;
+
+public class IdGenerationIntegrationTest {
+
+ private static EntityManager entityManager;
+ private static UserService service;
+
+ @BeforeClass
+ public static void setup() {
+ EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa-h2-id-generation");
+ entityManager = factory.createEntityManager();
+ service = new UserService(entityManager);
+ }
+
+ @Test
+ public void whenNewUserIsPersisted_thenEntityHasNoId() {
+ User user = new User();
+ user.setUsername("test");
+ user.setPassword(UUID.randomUUID().toString());
+
+ long index = service.saveUser(user);
+ Assert.assertEquals(0L, index);
+ }
+
+ @Test
+ public void whenTransactionIsControlled_thenEntityHasId() {
+ User user = new User();
+ user.setUsername("test");
+ user.setPassword(UUID.randomUUID().toString());
+
+ entityManager.getTransaction().begin();
+ long index = service.saveUser(user);
+ entityManager.getTransaction().commit();
+
+ Assert.assertEquals(2L, index);
+ }
+
+}
diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/returnmultipleentities/ReturnMultipleEntitiesIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/returnmultipleentities/ReturnMultipleEntitiesIntegrationTest.java
new file mode 100644
index 0000000000..eb71060f22
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/returnmultipleentities/ReturnMultipleEntitiesIntegrationTest.java
@@ -0,0 +1,84 @@
+package com.baeldung.jpa.returnmultipleentities;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ReturnMultipleEntitiesIntegrationTest {
+ private static EntityManagerFactory factory;
+ private static EntityManager entityManager;
+ private ReportRepository reportRepository;
+
+ @Before
+ public void setup() {
+ factory = Persistence.createEntityManagerFactory("jpa-h2-return-multiple-entities");
+ entityManager = factory.createEntityManager();
+ reportRepository = new ReportRepository();
+ populateH2DB();
+ }
+
+ @Test
+ public void whenQueryingForMultipleEntitiesInOneQuery_thenJPAReturnsMultipleEntitiesInCorrectOrder() {
+ List
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/AdditionalWebConfiguration.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/AdditionalWebConfiguration.java
new file mode 100644
index 0000000000..22b41a28e0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/AdditionalWebConfiguration.java
@@ -0,0 +1,9 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWarDeployment;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnWarDeployment
+public class AdditionalWebConfiguration {
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/ConditionalUtils.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/ConditionalUtils.java
new file mode 100644
index 0000000000..7294d843d1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/ConditionalUtils.java
@@ -0,0 +1,20 @@
+package com.baeldung.annotations.conditional;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.springframework.boot.system.JavaVersion;
+
+public class ConditionalUtils {
+
+ public static boolean isWindows() {
+ return SystemUtils.IS_OS_WINDOWS;
+ }
+
+ public static boolean isJava8() {
+ return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT);
+ }
+
+ public static boolean isJava9() {
+ return JavaVersion.getJavaVersion().equals(JavaVersion.NINE);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/DevEnvLoggingConfiguration.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/DevEnvLoggingConfiguration.java
new file mode 100644
index 0000000000..39f1d568f0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/DevEnvLoggingConfiguration.java
@@ -0,0 +1,16 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Conditional(IsDevEnvCondition.class)
+public class DevEnvLoggingConfiguration {
+
+ @Bean
+ @Conditional(IsDevEnvCondition.class)
+ LoggingService loggingService() {
+ return new LoggingService();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/IsDevEnvCondition.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/IsDevEnvCondition.java
new file mode 100644
index 0000000000..768cd9f6ed
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/IsDevEnvCondition.java
@@ -0,0 +1,13 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class IsDevEnvCondition implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ return "dev".equals(System.getProperty("env"));
+ }
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/IsWindowsCondition.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/IsWindowsCondition.java
new file mode 100644
index 0000000000..24aa4f4b1c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/IsWindowsCondition.java
@@ -0,0 +1,13 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class IsWindowsCondition implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ return ConditionalUtils.isWindows();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8Condition.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8Condition.java
new file mode 100644
index 0000000000..c5f5e16d52
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8Condition.java
@@ -0,0 +1,13 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class Java8Condition implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ return ConditionalUtils.isJava8();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8DependedService.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8DependedService.java
new file mode 100644
index 0000000000..ab76dcd930
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8DependedService.java
@@ -0,0 +1,9 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.context.annotation.Conditional;
+import org.springframework.stereotype.Service;
+
+@Service
+@Conditional(Java8Condition.class)
+public class Java8DependedService {
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8OrJava9.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8OrJava9.java
new file mode 100644
index 0000000000..77c501ed08
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java8OrJava9.java
@@ -0,0 +1,16 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
+import org.springframework.context.annotation.Conditional;
+
+public class Java8OrJava9 extends AnyNestedCondition {
+ Java8OrJava9() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ @Conditional(Java8Condition.class)
+ static class Java8 { }
+
+ @Conditional(Java9Condition.class)
+ static class Java9 { }
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java9Condition.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java9Condition.java
new file mode 100644
index 0000000000..2afa8b25a7
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/Java9Condition.java
@@ -0,0 +1,13 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class Java9Condition implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ return ConditionalUtils.isJava9();
+ }
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/LoggingService.java b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/LoggingService.java
new file mode 100644
index 0000000000..e104ec86e1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional/LoggingService.java
@@ -0,0 +1,19 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.system.JavaVersion;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.stereotype.Service;
+
+@Service
+@Conditional({IsDevEnvCondition.class, IsWindowsCondition.class, Java8Condition.class})
+@ConditionalOnProperty(
+ value = "logging.enabled",
+ havingValue = "true",
+ matchIfMissing = true)
+@ConditionalOnExpression("${logging.enabled:true} and '${logging.level}'.equals('DEBUG')")
+@ConditionalOnJava(JavaVersion.EIGHT)
+public class LoggingService {
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/ConditionalTestConfiguration.java b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/ConditionalTestConfiguration.java
new file mode 100644
index 0000000000..6b23433422
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/ConditionalTestConfiguration.java
@@ -0,0 +1,10 @@
+package com.baeldung.annotations.conditional;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+
+@TestConfiguration
+@ComponentScan("com.baeldung.annotations.conditional")
+public class ConditionalTestConfiguration {
+
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/DevEnvLoggingConfigurationUnitTest.java b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/DevEnvLoggingConfigurationUnitTest.java
new file mode 100644
index 0000000000..e7c1975127
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/DevEnvLoggingConfigurationUnitTest.java
@@ -0,0 +1,52 @@
+package com.baeldung.annotations.conditional;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+
+public class DevEnvLoggingConfigurationUnitTest {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
+
+ @Test
+ public void whenDevEnvEnabled_thenDevEnvLoggingConfigurationAndLoggingServiceShouldBeCreated() {
+ System.setProperty("env", "dev");
+
+ contextRunner
+ .withUserConfiguration(ConditionalTestConfiguration.class)
+ .run(context ->
+ Assertions.assertNotNull(
+ context.getBean(DevEnvLoggingConfiguration.class)
+ )
+ );
+ contextRunner
+ .withUserConfiguration(ConditionalTestConfiguration.class)
+ .run(context ->
+ Assertions.assertNotNull(
+ context.getBean(LoggingService.class)
+ )
+ );
+ }
+
+ @Test
+ public void whenDevEnvNotEnabled_thenDevEnvLoggingConfigurationAndLoggingServiceShouldNotBeCreated() {
+ System.setProperty("env", "not-dev");
+
+ contextRunner
+ .withUserConfiguration(ConditionalTestConfiguration.class)
+ .run(context ->
+ Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
+ context.getBean(DevEnvLoggingConfiguration.class)
+ )
+ );
+ contextRunner
+ .withUserConfiguration(ConditionalTestConfiguration.class)
+ .run(context ->
+ Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
+ context.getBean(LoggingService.class)
+ )
+ );
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/IsDevEnvConditionUnitTest.java b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/IsDevEnvConditionUnitTest.java
new file mode 100644
index 0000000000..5302fc79dd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/IsDevEnvConditionUnitTest.java
@@ -0,0 +1,33 @@
+package com.baeldung.annotations.conditional;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class IsDevEnvConditionUnitTest {
+
+ @Test
+ public void whenDevEnvEnabled_thenDevEnvConditionShouldPass() {
+ System.setProperty("env", "dev");
+
+ Assertions.assertTrue(
+ new IsDevEnvCondition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+
+ @Test
+ public void whenDevEnvNotEnabled_thenDevEnvConditionShouldNotPass() {
+ System.setProperty("env", "not-dev");
+
+ Assertions.assertFalse(
+ new IsDevEnvCondition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/IsWindowsConditionUnitTest.java b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/IsWindowsConditionUnitTest.java
new file mode 100644
index 0000000000..45ae65a0e2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/IsWindowsConditionUnitTest.java
@@ -0,0 +1,39 @@
+package com.baeldung.annotations.conditional;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class IsWindowsConditionUnitTest {
+
+ @Test
+ public void whenOnWindows_thenIsWindowsConditionShouldPass() {
+ try (MockedStatic theMock = Mockito.mockStatic(ConditionalUtils.class)) {
+ theMock.when(ConditionalUtils::isWindows)
+ .thenReturn(true);
+ Assertions.assertTrue(
+ new IsWindowsCondition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+
+ }
+
+ @Test
+ public void whenNotOnWindows_thenIsWindowsConditionShouldNotPass() {
+ try (MockedStatic theMock = Mockito.mockStatic(ConditionalUtils.class)) {
+ theMock.when(ConditionalUtils::isWindows)
+ .thenReturn(false);
+ Assertions.assertFalse(
+ new IsWindowsCondition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/Java8ConditionUnitTest.java b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/Java8ConditionUnitTest.java
new file mode 100644
index 0000000000..0d1b1eded6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/Java8ConditionUnitTest.java
@@ -0,0 +1,39 @@
+package com.baeldung.annotations.conditional;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class Java8ConditionUnitTest {
+
+ @Test
+ public void whenOnJava8_thenJava8ConditionShouldPass() {
+ try (MockedStatic theMock = Mockito.mockStatic(ConditionalUtils.class)) {
+ theMock.when(ConditionalUtils::isJava8)
+ .thenReturn(true);
+ Assertions.assertTrue(
+ new Java8Condition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+
+ }
+
+ @Test
+ public void whenNotOnJava8_thenJava8ConditionShouldNotPass() {
+ try (MockedStatic theMock = Mockito.mockStatic(ConditionalUtils.class)) {
+ theMock.when(ConditionalUtils::isJava8)
+ .thenReturn(false);
+ Assertions.assertFalse(
+ new Java8Condition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/Java9ConditionUnitTest.java b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/Java9ConditionUnitTest.java
new file mode 100644
index 0000000000..ce277e81fe
--- /dev/null
+++ b/spring-boot-modules/spring-boot-annotations/src/test/java/com/baeldung/annotations/conditional/Java9ConditionUnitTest.java
@@ -0,0 +1,39 @@
+package com.baeldung.annotations.conditional;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class Java9ConditionUnitTest {
+
+ @Test
+ public void whenOnJava9_thenJava9ConditionShouldPass() {
+ try (MockedStatic theMock = Mockito.mockStatic(ConditionalUtils.class)) {
+ theMock.when(ConditionalUtils::isJava9)
+ .thenReturn(true);
+ Assertions.assertTrue(
+ new Java9Condition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+
+ }
+
+ @Test
+ public void whenNotOnJava9_thenJava9ConditionShouldNotPass() {
+ try (MockedStatic theMock = Mockito.mockStatic(ConditionalUtils.class)) {
+ theMock.when(ConditionalUtils::isJava9)
+ .thenReturn(false);
+ Assertions.assertFalse(
+ new Java9Condition().matches(
+ Mockito.mock(ConditionContext.class), Mockito.mock(AnnotatedTypeMetadata.class)
+ )
+ );
+ }
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-basic-customization-2/README.md b/spring-boot-modules/spring-boot-basic-customization-2/README.md
index bf7e4abb76..f041c1d38a 100644
--- a/spring-boot-modules/spring-boot-basic-customization-2/README.md
+++ b/spring-boot-modules/spring-boot-basic-customization-2/README.md
@@ -5,3 +5,4 @@ This module contains articles about Spring Boot customization 2
### Relevant Articles:
- [DispatcherServlet and web.xml in Spring Boot](https://www.baeldung.com/spring-boot-dispatcherservlet-web-xml)
+ - [XML Defined Beans in Spring Boot](https://www.baeldung.com/spring-boot-xml-beans)
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-basic-customization-2/pom.xml b/spring-boot-modules/spring-boot-basic-customization-2/pom.xml
index d42a7fd3de..8c1bc22600 100644
--- a/spring-boot-modules/spring-boot-basic-customization-2/pom.xml
+++ b/spring-boot-modules/spring-boot-basic-customization-2/pom.xml
@@ -23,7 +23,10 @@
org.springframework.boot
spring-boot-starter-test
- test
+
+
+ junit
+ junit
diff --git a/spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/Pojo.java b/spring-boot-modules/spring-boot-basic-customization-2/src/main/java/com/baeldung/springbootxml/Pojo.java
similarity index 100%
rename from spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/Pojo.java
rename to spring-boot-modules/spring-boot-basic-customization-2/src/main/java/com/baeldung/springbootxml/Pojo.java
diff --git a/spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplication.java b/spring-boot-modules/spring-boot-basic-customization-2/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplication.java
similarity index 100%
rename from spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplication.java
rename to spring-boot-modules/spring-boot-basic-customization-2/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplication.java
diff --git a/spring-boot-modules/spring-boot-basic-customization-2/src/main/resources/application.properties b/spring-boot-modules/spring-boot-basic-customization-2/src/main/resources/application.properties
index e69de29bb2..ab9de92c82 100644
--- a/spring-boot-modules/spring-boot-basic-customization-2/src/main/resources/application.properties
+++ b/spring-boot-modules/spring-boot-basic-customization-2/src/main/resources/application.properties
@@ -0,0 +1 @@
+sample=string loaded from properties!
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-xml/src/main/resources/beans.xml b/spring-boot-modules/spring-boot-basic-customization-2/src/main/resources/beans.xml
similarity index 100%
rename from spring-boot-modules/spring-boot-xml/src/main/resources/beans.xml
rename to spring-boot-modules/spring-boot-basic-customization-2/src/main/resources/beans.xml
diff --git a/spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplicationIntegrationTest.java b/spring-boot-modules/spring-boot-basic-customization-2/src/main/test/com/baeldung/springbootxml/SpringBootXmlApplicationIntegrationTest.java
similarity index 99%
rename from spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplicationIntegrationTest.java
rename to spring-boot-modules/spring-boot-basic-customization-2/src/main/test/com/baeldung/springbootxml/SpringBootXmlApplicationIntegrationTest.java
index 2c3993d0d8..f3060de82a 100644
--- a/spring-boot-modules/spring-boot-xml/src/main/java/com/baeldung/springbootxml/SpringBootXmlApplicationIntegrationTest.java
+++ b/spring-boot-modules/spring-boot-basic-customization-2/src/main/test/com/baeldung/springbootxml/SpringBootXmlApplicationIntegrationTest.java
@@ -1,5 +1,6 @@
package com.baeldung.springbootxml;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
diff --git a/spring-boot-modules/spring-boot-cassandre/README.md b/spring-boot-modules/spring-boot-cassandre/README.md
new file mode 100644
index 0000000000..4dfef587db
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/README.md
@@ -0,0 +1,11 @@
+# Cassandre trading bot example
+
+This project is an example of a trading bot developed with Cassandre
+
+## Running the examples
+
+* `mvn test` - Run strategy backtesting
+* `mvn spring-boot:run` - Run the bot
+
+## Relevant Articles
+- [Build a Trading Bot with Cassandre Spring Boot Starter](https://www.baeldung.com/cassandre-spring-boot-trading-bot)
diff --git a/spring-boot-modules/spring-boot-cassandre/pom.xml b/spring-boot-modules/spring-boot-cassandre/pom.xml
new file mode 100644
index 0000000000..75163d2c2b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/pom.xml
@@ -0,0 +1,68 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.5
+
+
+ com.example
+ demo
+ 0.0.1-SNAPSHOT
+ Cassandre trading bot tutorial
+ Cassandre trading bot tutorial
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+
+ tech.cassandre.trading.bot
+ cassandre-trading-bot-spring-boot-starter
+ 4.2.1
+
+
+ org.knowm.xchange
+ xchange-kucoin
+ 5.0.7
+
+
+ org.hsqldb
+ hsqldb
+ 2.5.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ tech.cassandre.trading.bot
+ cassandre-trading-bot-spring-boot-starter-test
+ 4.2.1
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.4.5
+
+
+
+
+
diff --git a/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/DemoApplication.java b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/DemoApplication.java
new file mode 100644
index 0000000000..094d95b93f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/DemoApplication.java
@@ -0,0 +1,13 @@
+package com.example.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/MyFirstStrategy.java b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/MyFirstStrategy.java
new file mode 100644
index 0000000000..ea8ae74aa6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/MyFirstStrategy.java
@@ -0,0 +1,66 @@
+package com.example.demo;
+
+import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.CLOSED;
+import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.OPENED;
+import static tech.cassandre.trading.bot.dto.util.CurrencyDTO.BTC;
+import static tech.cassandre.trading.bot.dto.util.CurrencyDTO.USDT;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import tech.cassandre.trading.bot.dto.market.TickerDTO;
+import tech.cassandre.trading.bot.dto.position.PositionDTO;
+import tech.cassandre.trading.bot.dto.position.PositionRulesDTO;
+import tech.cassandre.trading.bot.dto.user.AccountDTO;
+import tech.cassandre.trading.bot.dto.util.CurrencyPairDTO;
+import tech.cassandre.trading.bot.strategy.BasicCassandreStrategy;
+import tech.cassandre.trading.bot.strategy.CassandreStrategy;
+
+@CassandreStrategy
+public class MyFirstStrategy extends BasicCassandreStrategy {
+
+ private final Logger logger = LoggerFactory.getLogger(MyFirstStrategy.class);
+
+ @Override
+ public Set getRequestedCurrencyPairs() {
+ return Set.of(new CurrencyPairDTO(BTC, USDT));
+ }
+
+ @Override
+ public Optional getTradeAccount(Set accounts) {
+ return accounts.stream()
+ .filter(a -> "trade".equals(a.getName()))
+ .findFirst();
+ }
+
+ @Override
+ public void onTickerUpdate(TickerDTO ticker) {
+ logger.info("Received a new ticker : {}", ticker);
+
+ if (new BigDecimal("56000").compareTo(ticker.getLast()) == -1) {
+
+ if (canBuy(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"))) {
+ PositionRulesDTO rules = PositionRulesDTO.builder()
+ .stopGainPercentage(4f)
+ .stopLossPercentage(25f)
+ .build();
+ createLongPosition(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"), rules);
+ }
+
+ }
+ }
+
+ @Override
+ public void onPositionStatusUpdate(PositionDTO position) {
+ if (position.getStatus() == OPENED) {
+ logger.info("> New position opened : {}", position.getPositionId());
+ }
+ if (position.getStatus() == CLOSED) {
+ logger.info("> Position closed : {}", position.getDescription());
+ }
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-cassandre/src/main/resources/application.properties b/spring-boot-modules/spring-boot-cassandre/src/main/resources/application.properties
new file mode 100644
index 0000000000..5ecdabd47a
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/main/resources/application.properties
@@ -0,0 +1,22 @@
+#
+# Exchange configuration.
+cassandre.trading.bot.exchange.name=kucoin
+cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com
+cassandre.trading.bot.exchange.passphrase=cassandre
+cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998
+cassandre.trading.bot.exchange.secret=af080d55-afe3-47c9-8ec1-4b479fbcc5e7
+#
+# Modes
+cassandre.trading.bot.exchange.modes.sandbox=true
+cassandre.trading.bot.exchange.modes.dry=false
+#
+# Exchange API calls rates (ms or standard ISO 8601 duration like 'PT5S').
+cassandre.trading.bot.exchange.rates.account=2000
+cassandre.trading.bot.exchange.rates.ticker=2000
+cassandre.trading.bot.exchange.rates.trade=2000
+#
+# Database configuration.
+cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
+cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre
+cassandre.trading.bot.database.datasource.username=sa
+cassandre.trading.bot.database.datasource.password=
diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/DemoApplicationTests.java b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/DemoApplicationTests.java
new file mode 100644
index 0000000000..eaa99696e2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/DemoApplicationTests.java
@@ -0,0 +1,13 @@
+package com.example.demo;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class DemoApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/MyFirstStrategyUnitTest.java b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/MyFirstStrategyUnitTest.java
new file mode 100644
index 0000000000..bf7c353821
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/MyFirstStrategyUnitTest.java
@@ -0,0 +1,55 @@
+package com.example.demo;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.OPENED;
+import static tech.cassandre.trading.bot.dto.util.CurrencyDTO.USDT;
+
+import java.util.HashMap;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+
+import tech.cassandre.trading.bot.dto.util.CurrencyDTO;
+import tech.cassandre.trading.bot.dto.util.GainDTO;
+import tech.cassandre.trading.bot.test.mock.TickerFluxMock;
+
+@SpringBootTest
+@Import(TickerFluxMock.class)
+@DisplayName("Simple strategy test")
+public class MyFirstStrategyUnitTest {
+
+ private final Logger logger = LoggerFactory.getLogger(MyFirstStrategyUnitTest.class);
+
+ @Autowired
+ private MyFirstStrategy strategy;
+
+ @Autowired
+ private TickerFluxMock tickerFluxMock;
+
+ @Test
+ @DisplayName("Check gains")
+ public void whenTickersArrives_thenCheckGains() {
+ await().forever().until(() -> tickerFluxMock.isFluxDone());
+
+ final HashMap gains = strategy.getGains();
+
+ logger.info("Cumulated gains:");
+ gains.forEach((currency, gain) -> logger.info(currency + " : " + gain.getAmount()));
+
+ logger.info("Position still opened :");
+ strategy.getPositions()
+ .values()
+ .stream()
+ .filter(p -> p.getStatus().equals(OPENED))
+ .forEach(p -> logger.info(" - {} " + p.getDescription()));
+
+ assertTrue(gains.get(USDT).getPercentage() > 0);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/resources/application.properties b/spring-boot-modules/spring-boot-cassandre/src/test/resources/application.properties
new file mode 100644
index 0000000000..3d6feb09d7
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/test/resources/application.properties
@@ -0,0 +1,22 @@
+#
+# Exchange configuration.
+cassandre.trading.bot.exchange.name=kucoin
+cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com
+cassandre.trading.bot.exchange.passphrase=cassandre
+cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998
+cassandre.trading.bot.exchange.secret=af080d55-afe3-47c9-8ec1-4b479fbcc5e7
+#
+# Modes
+cassandre.trading.bot.exchange.modes.sandbox=true
+cassandre.trading.bot.exchange.modes.dry=true
+#
+# Exchange API calls rates (ms or standard ISO 8601 duration like 'PT5S').
+cassandre.trading.bot.exchange.rates.account=2000
+cassandre.trading.bot.exchange.rates.ticker=2000
+cassandre.trading.bot.exchange.rates.trade=2000
+#
+# Database configuration.
+cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
+cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre
+cassandre.trading.bot.database.datasource.username=sa
+cassandre.trading.bot.database.datasource.password=
diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/resources/tickers-btc-usdt.tsv b/spring-boot-modules/spring-boot-cassandre/src/test/resources/tickers-btc-usdt.tsv
new file mode 100644
index 0000000000..b89bd96bdc
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/test/resources/tickers-btc-usdt.tsv
@@ -0,0 +1,89 @@
+1612569600 38294.4 39195.5 40964.2 38217.5 3882.29460938 153897343.463150723
+1612656000 39195.4 38807.6 39623.6 37341.4 2389.96820017 91972455.834535369
+1612742400 38807.6 46373.5 46767.9 38010 4971.54731481 212132648.426718929
+1612828800 46374.6 46434.8 48139.3 44961 4330.72854712 201891604.027160303
+1612915200 46430 44812.2 47300 43687.5 4351.84907778 198189685.592028516
+1613001600 44806.1 47941.5 48647.6 44005.8 4045.91883504 188171651.974437139
+1613088000 47963.1 47310.7 48958.8 46181.2 3356.01832119 159561721.419695848
+1613174400 47305.4 47152.6 48120.5 46225.5 2740.99221759 129227867.922246174
+1613260800 47152.5 48591.9 49686.9 47026.3 3359.4690565 163299915.839307312
+1613347200 48587.2 47904.4 49003.6 42841.6 3974.98461358 188990056.26923591
+1613433600 47913.1 49147.7 50619.3 47023.9 3599.85370182 176084748.845657596
+1613520000 49147.7 52118.1 52609.6 48931.1 3356.85082847 170893567.530348564
+1613606400 52114.3 51568.9 52522.9 50906.4 2183.18379408 113272339.172174965
+1613692800 51561.1 55890.5 56317.7 50727 3749.6920105 200656740.865959032
+1613779200 55893.6 55851.5 57622.6 53463.3 3394.87226216 190744601.429330887
+1613865600 55851.4 57423 58336.3 55489.6 2514.02340013 143658132.671448082
+1613952000 57420.6 54096.6 57517.8 44160 6125.32442907 330513978.457310237
+1614038400 54085.9 48908.3 54174.2 44900 8048.96505298 389277314.445372085
+1614124800 48902.9 49685.2 51361.9 47003.2 4816.75027676 239303706.844272809
+1614211200 49676.5 47082.7 52019.6 46624.4 3701.80236678 184044004.383578525
+1614297600 47082 46289.6 48408.8 44135 5329.77125908 247604118.914146591
+1614384000 46290.2 46114.4 48381.9 44836.5 2872.64640734 134946360.020429589
+1614470400 46111.3 45141.6 46626.1 43004.3 3940.17863714 175990962.484551548
+1614556800 45136.5 49590.3 49771.3 44958.9 3548.51026561 169389196.772247159
+1614643200 49590.3 48441.2 50201.6 47052.8 2936.94454126 142575425.463057812
+1614729600 48440.6 50345.5 52623.9 48100 3177.38943911 160801620.821885745
+1614816000 50347.6 48374.5 51762.1 47505.7 3624.17683614 178873453.27515484
+1614902400 48374.5 48758.9 49450 46189.8 3697.34556922 176318969.507294567
+1614988800 48746.9 48871.9 49255.1 47001 1949.15311354 94201823.810314647
+1615075200 48885 50930.4 51424.7 48885 2444.3584982 122962479.787996993
+1615161600 50956.6 52377 52387.5 49287.5 2710.99151191 137751640.241286989
+1615248000 52376.9 54867.6 54867.6 51833.8 3070.93581512 165487483.114064122
+1615334400 54867.5 55865.5 57364 52911.4 4049.50553851 224565244.752334892
+1615420800 55863.7 57781.1 58150 54238 3403.69441456 191915265.020541521
+1615507200 57781 57238.5 58057.5 55013.7 4031.0376629 228810606.091302364
+1615593600 57220.7 61180.9 61815.3 56059.3 4394.62318443 259602986.875738328
+1615680000 61174.3 59000 61700 59000 3084.33952274 186155667.656432156
+1615766400 59000 55607.1 60632.9 54525.6 5910.33518227 338468393.188725572
+1615852800 55607.1 56880.3 56918.9 53240.3 7410.49057723 409052587.523700888
+1615939200 56880.3 58875.8 58951.8 54147.5 5828.79026943 328135601.648660052
+1616025600 58882.9 57648.9 60107.7 57000 5073.7458698 297279816.540519693
+1616112000 57648.9 58024.2 59450.1 56071 3727.09434161 217005823.723994618
+1616198400 58024.3 58113.5 59874.6 57825.6 2746.52973805 161565114.165299707
+1616284800 58113.5 57350.2 58591.6 55501 3265.35649781 186845535.507151609
+1616371200 57345.3 54096.1 58415.5 53667 4219.99501831 237141977.003568352
+1616457600 54086.8 54348.4 55823.1 52986.3 4374.34046303 239135883.538398977
+1616544000 54348.4 52307.4 57200 51499.6 6416.76024581 351202326.218690674
+1616630400 52307.1 51301.7 53239.1 50455 7242.6466396 375950351.557038048
+1616716800 51301.7 55032 55062.5 51225.3 4609.48192944 245299757.451540308
+1616803200 55031.9 55820.4 56628.6 53967.5 3634.73588532 200758048.816804103
+1616889600 55820.3 55772.9 56541 54666.6 3158.20452681 176119911.151714842
+1616976000 55772.8 57628.9 58400.5 54926.5 4413.63121553 251384747.301649587
+1617062400 57630.8 58754.6 59351.9 57072.8 3563.87315049 208118726.050535887
+1617148800 58753.2 58745.9 59800 56357.5 4921.45848213 288469053.074870873
+1617235200 58745.5 58735.7 59487.1 57879 3163.98213108 186078130.901422269
+1617321600 58735.7 58963.6 60179.1 58460.7 2553.76427314 151446539.609794648
+1617408000 58963.6 57058.3 59795 56721.2 2512.19109578 147434403.06515736
+1617494400 57052.5 58201.4 58481.2 56432.6 2069.14670128 119228330.17272614
+1617580800 58201.4 59116.2 59254.1 56501 3003.76043377 174821106.684799505
+1617667200 59116.2 57988.3 59497.5 57304.8 2964.86183859 173169186.845682699
+1617753600 57988.3 55958.2 58668.6 55439 5277.04906389 299996660.411940246
+1617840000 55958.2 58076.7 58141 55700.6 3175.60482079 181817013.517575328
+1617926400 58076.7 58131.6 58900 57666.9 3516.19104669 204849717.059779284
+1618012800 58138.2 59770.2 61350 57902.1 5533.50675561 332014577.538990658
+1618099200 59770.2 60007.6 60687.4 59247.6 3896.37426019 233158562.799039154
+1618185600 60007.6 59863.4 61270.7 59417.4 4611.409014 277430208.743380477
+1618272000 59863.4 63578.7 63759.7 59815 6906.310253 430518557.569547626
+1618358400 63578.7 62958.7 64840 61000 7696.509177 487298143.928065301
+1618444800 62954.4 63152.6 63772.1 62023.9 4709.82427144 296178401.81115496
+1618531200 63152.6 61342.6 63509.7 59930.8 8295.32523869 510423835.691643255
+1618617600 61342.7 59995.2 62497.8 59599.6 5367.42979289 328364887.709585395
+1618704000 59995.3 56150.6 60409.5 49001 11485.97101449 637797282.448645379
+1618790400 56152.7 55618.8 57583.4 54205.2 7721.306905 432634348.931871989
+1618876800 55618.7 56427.8 57061.6 53328 8677.75606016 480164200.559836543
+1618963200 56426.1 53793.6 56761.7 53602 6240.82191836 345339357.806167462
+1619049600 53793.5 51696.4 55474.7 50400 8879.16016304 475394174.249706678
+1619136000 51691.2 51102.7 52112.1 47502.1 8885.07060366 441295812.644904319
+1619222400 51109.8 50033 51157.9 48676.5 4833.41744745 241336360.887795675
+1619308800 50041.5 49086.9 50554.6 46966.2 4805.34664069 237153315.222670555
+1619395200 49069 54000.2 54336.4 48775.8 6695.12934907 353727728.269533971
+1619481600 53997.1 55014.3 55439 53240.8 4344.22291318 237020455.905144335
+1619568000 55014.2 54833.2 56399.1 53808.3 4801.04618634 262912695.604761319
+1619654400 54833.1 53558.4 55181.2 52340.1 4356.05177188 234153663.397444462
+1619740800 53558.5 57697.3 57936.4 53042.6 5000.47557303 277531927.921795199
+1619827200 57697.3 57794.7 58471.4 57006.3 3639.78966647 210179438.189007639
+1619913600 57794.7 56568.5 57903.7 56044.3 3508.52428767 199206958.05741809
+1620000000 56568.5 57159.7 58977.9 56451.3 4780.43387226 276554749.540429296
+1620086400 57159.7 53196.3 57188.2 53083.3 7079.55804728 390469293.396018923
+1620172800 53196.3 57834.5 57979.7 52888 4224.63060355 233779565.506303973
diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/resources/user-trade.tsv b/spring-boot-modules/spring-boot-cassandre/src/test/resources/user-trade.tsv
new file mode 100644
index 0000000000..d0fa4e9767
--- /dev/null
+++ b/spring-boot-modules/spring-boot-cassandre/src/test/resources/user-trade.tsv
@@ -0,0 +1,3 @@
+BTC 1
+USDT 100000
+ETH 10
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-exceptions/pom.xml b/spring-boot-modules/spring-boot-exceptions/pom.xml
index c0c335f55c..9866c418be 100644
--- a/spring-boot-modules/spring-boot-exceptions/pom.xml
+++ b/spring-boot-modules/spring-boot-exceptions/pom.xml
@@ -15,6 +15,14 @@
../
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
spring-boot-exceptions
diff --git a/spring-boot-modules/spring-boot-exceptions/src/test/java/com/baeldung/applicationcontextexception/MainEntryPoint.java b/spring-boot-modules/spring-boot-exceptions/src/test/java/com/baeldung/applicationcontextexception/MainEntryPoint.java
new file mode 100644
index 0000000000..c187399636
--- /dev/null
+++ b/spring-boot-modules/spring-boot-exceptions/src/test/java/com/baeldung/applicationcontextexception/MainEntryPoint.java
@@ -0,0 +1,14 @@
+package com.baeldung.applicationcontextexception;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+//Remove this annotation to produce ApplicationContextException error
+@SpringBootApplication
+public class MainEntryPoint {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MainEntryPoint.class, args);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/WebController.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/WebController.java
index 3bafe1f195..bbd96c8135 100644
--- a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/WebController.java
+++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/WebController.java
@@ -8,6 +8,8 @@ import java.security.Principal;
import org.springframework.beans.factory.annotation.Autowired;
+import javax.servlet.http.HttpServletRequest;
+
@Controller
public class WebController {
@@ -19,6 +21,12 @@ public class WebController {
return "external";
}
+ @GetMapping("/logout")
+ public String logout(HttpServletRequest request) throws Exception {
+ request.logout();
+ return "redirect:/";
+ }
+
@GetMapping(path = "/customers")
public String customers(Principal principal, Model model) {
addCustomers();
diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/resources/templates/customers.html b/spring-boot-modules/spring-boot-keycloak/src/main/resources/templates/customers.html
index 5a060d31da..de2df93ef1 100644
--- a/spring-boot-modules/spring-boot-keycloak/src/main/resources/templates/customers.html
+++ b/spring-boot-modules/spring-boot-keycloak/src/main/resources/templates/customers.html
@@ -27,6 +27,7 @@
+ Logout
+ Hello
+
diff --git a/spring-boot-modules/spring-boot-mvc-3/README.md b/spring-boot-modules/spring-boot-mvc-3/README.md
index bc3eb9e496..f9c6989b3c 100644
--- a/spring-boot-modules/spring-boot-mvc-3/README.md
+++ b/spring-boot-modules/spring-boot-mvc-3/README.md
@@ -9,4 +9,5 @@ This module contains articles about Spring Web MVC in Spring Boot projects.
- [Spring MVC Async vs Spring WebFlux](https://www.baeldung.com/spring-mvc-async-vs-webflux)
- [Differences in @Valid and @Validated Annotations in Spring](https://www.baeldung.com/spring-valid-vs-validated)
- [CharacterEncodingFilter In SpringBoot](https://www.baeldung.com/spring-boot-characterencodingfilter)
+- [HandlerInterceptors vs. Filters in Spring MVC](https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter)
- More articles: [[prev -->]](/spring-boot-modules/spring-boot-mvc-2)
diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/FilterInterceptorApp.java b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/FilterInterceptorApp.java
new file mode 100644
index 0000000000..b1e6badd43
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/FilterInterceptorApp.java
@@ -0,0 +1,11 @@
+package com.baeldung.filtersinterceptors;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication(scanBasePackages = "com.baeldung.filtersinterceptors")
+public class FilterInterceptorApp {
+ public static void main(String[] args) {
+ SpringApplication.run(FilterInterceptorApp.class, args);
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/HelloConroller.java b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/HelloConroller.java
new file mode 100644
index 0000000000..db2da63d43
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/HelloConroller.java
@@ -0,0 +1,19 @@
+package com.baeldung.filtersinterceptors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class HelloConroller {
+
+ private Logger logger = LoggerFactory.getLogger(HelloConroller.class);
+
+ @GetMapping("/hello")
+ public String hello() {
+ logger.info("Hello from the controller");
+ return "hello";
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/LogFilter.java b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/LogFilter.java
new file mode 100644
index 0000000000..dc78cfbbb9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/LogFilter.java
@@ -0,0 +1,26 @@
+package com.baeldung.filtersinterceptors;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class LogFilter implements Filter {
+
+ private Logger logger = LoggerFactory.getLogger(LogFilter.class);
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ logger.info("Hello from: " + request.getLocalAddr());
+ chain.doFilter(request, response);
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/LogInterceptor.java b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/LogInterceptor.java
new file mode 100644
index 0000000000..b43b69415a
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/LogInterceptor.java
@@ -0,0 +1,32 @@
+package com.baeldung.filtersinterceptors;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+public class LogInterceptor implements HandlerInterceptor {
+
+ private Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ logger.info("preHandle");
+ return true;
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+ logger.info("postHandle");
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ logger.info("afterCompletion");
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/WebMvcConfig.java b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/WebMvcConfig.java
new file mode 100644
index 0000000000..9f4c1c2166
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc-3/src/main/java/com/baeldung/filtersinterceptors/WebMvcConfig.java
@@ -0,0 +1,15 @@
+package com.baeldung.filtersinterceptors;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(new LogInterceptor());
+ }
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc-3/src/main/resources/templates/hello.html b/spring-boot-modules/spring-boot-mvc-3/src/main/resources/templates/hello.html
new file mode 100644
index 0000000000..9a9b0e707b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc-3/src/main/resources/templates/hello.html
@@ -0,0 +1,10 @@
+
+