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-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-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-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-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-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/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
index 4585b6cc86..970faaac88 100644
--- a/core-java-modules/core-java-security-3/README.md
+++ b/core-java-modules/core-java-security-3/README.md
@@ -4,5 +4,5 @@ This module contains articles about core Java Security
### Relevant Articles:
-- [Secret Key and String Conversion in Java](https://www.baeldung.com/secret-key-and-string-conversion-in-java/)
+- [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/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/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-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/persistence-modules/java-jpa-3/README.md b/persistence-modules/java-jpa-3/README.md
index e607043880..c024d7c540 100644
--- a/persistence-modules/java-jpa-3/README.md
+++ b/persistence-modules/java-jpa-3/README.md
@@ -11,3 +11,5 @@ This module contains articles about the Java Persistence API (JPA) in Java.
- [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/src/main/java/com/baeldung/jpa/uniqueconstraints/Address.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Address.java
new file mode 100644
index 0000000000..b20de6a471
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Address.java
@@ -0,0 +1,37 @@
+package com.baeldung.jpa.uniqueconstraints;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table
+public class Address implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String streetAddress;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getStreetAddress() {
+ return streetAddress;
+ }
+
+ public void setStreetAddress(String streetAddress) {
+ this.streetAddress = streetAddress;
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Person.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Person.java
new file mode 100644
index 0000000000..c5df90df73
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Person.java
@@ -0,0 +1,116 @@
+package com.baeldung.jpa.uniqueconstraints;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+@Entity
+@Table(uniqueConstraints = { @UniqueConstraint(name = "UniqueNumberAndStatus", columnNames = { "personNumber", "isActive" }),
+ @UniqueConstraint(name = "UniqueSecurityAndDepartment", columnNames = { "securityNumber", "departmentCode" }),
+ @UniqueConstraint(name = "UniqueNumberAndAddress", columnNames = { "personNumber", "address" }) })
+public class Person implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String name;
+
+ private String password;
+
+ @Column(unique = true)
+ private String email;
+
+ @Column(unique = true)
+ private Long personNumber;
+
+ private Boolean isActive;
+
+ private String securityNumber;
+
+ private String departmentCode;
+
+ @Column(unique = true)
+ @JoinColumn(name = "addressId", referencedColumnName = "id")
+ private Address address;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Long getPersonNumber() {
+ return personNumber;
+ }
+
+ public void setPersonNumber(Long personNumber) {
+ this.personNumber = personNumber;
+ }
+
+ public Boolean getIsActive() {
+ return isActive;
+ }
+
+ public void setIsActive(Boolean isActive) {
+ this.isActive = isActive;
+ }
+
+ public String getScode() {
+ return securityNumber;
+ }
+
+ public void setScode(String scode) {
+ this.securityNumber = scode;
+ }
+
+ public String getDcode() {
+ return departmentCode;
+ }
+
+ public void setDcode(String dcode) {
+ this.departmentCode = dcode;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml b/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml
index 666fc1500a..1166aaca71 100644
--- a/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml
+++ b/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml
@@ -113,6 +113,22 @@
+
+ org.hibernate.jpa.HibernatePersistenceProvider
+ com.baeldung.jpa.uniqueconstraints.Person
+ com.baeldung.jpa.uniqueconstraints.Address
+ true
+
+
+
+
+
+
+
+
+
+
+
org.hibernate.jpa.HibernatePersistenceProvider
com.baeldung.jpa.returnmultipleentities.Channel
diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueColumnIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueColumnIntegrationTest.java
new file mode 100644
index 0000000000..ca776ba00b
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueColumnIntegrationTest.java
@@ -0,0 +1,119 @@
+package com.baeldung.jpa.uniqueconstraints;
+
+import java.util.Optional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.hibernate.exception.ConstraintViolationException;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class UniqueColumnIntegrationTest {
+
+ private static EntityManagerFactory factory;
+ private static EntityManager entityManager;
+
+ @BeforeAll
+ public static void setup() {
+ factory = Persistence.createEntityManagerFactory("jpa-unique-constraints");
+ entityManager = factory.createEntityManager();
+ }
+
+ @Test
+ public void whenPersistPersonWithSameNumber_thenConstraintViolationException() {
+ Person person1 = new Person();
+ person1.setPersonNumber(2000L);
+ person1.setEmail("john.beth@gmail.com");
+
+ Person person2 = new Person();
+ person2.setPersonNumber(2000L);
+ person2.setEmail("anthony.green@gmail.com");
+
+ entityManager.getTransaction().begin();
+ entityManager.persist(person1);
+ entityManager.getTransaction().commit();
+
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(person2);
+ entityManager.getTransaction().commit();
+ Assert.fail("Should raise an exception - unique key violation");
+ } catch (Exception ex) {
+ Assert.assertTrue(Optional.of(ex)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause)
+ .filter(x -> x instanceof ConstraintViolationException)
+ .isPresent());
+ } finally {
+ entityManager.getTransaction().rollback();
+ }
+ }
+
+ @Test
+ public void whenPersistPersonWithSameEmail_thenConstraintViolationException() {
+ Person person1 = new Person();
+ person1.setPersonNumber(4000L);
+ person1.setEmail("timm.beth@gmail.com");
+
+ Person person2 = new Person();
+ person2.setPersonNumber(3000L);
+ person2.setEmail("timm.beth@gmail.com");
+
+ entityManager.getTransaction().begin();
+ entityManager.persist(person1);
+ entityManager.getTransaction().commit();
+
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(person2);
+ entityManager.getTransaction().commit();
+ Assert.fail("Should raise an exception - unique key violation");
+ } catch (Exception ex) {
+ Assert.assertTrue(Optional.of(ex)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause)
+ .filter(x -> x instanceof ConstraintViolationException)
+ .isPresent());
+ } finally {
+ entityManager.getTransaction().rollback();
+ }
+ }
+
+ @Test
+ public void whenPersistPersonWithSameAddress_thenConstraintViolationException() {
+ Person person1 = new Person();
+ person1.setPersonNumber(5000L);
+ person1.setEmail("chris.beck@gmail.com");
+
+ Address address1 = new Address();
+ address1.setStreetAddress("20 Street");
+ person1.setAddress(address1);
+
+ Person person2 = new Person();
+ person2.setPersonNumber(6000L);
+ person2.setEmail("mark.jonson@gmail.com");
+ person2.setAddress(address1);
+
+ entityManager.getTransaction().begin();
+ entityManager.persist(person1);
+ entityManager.getTransaction().commit();
+
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(person2);
+ entityManager.getTransaction().commit();
+ Assert.fail("Should raise an exception - unique key violation");
+ } catch (Exception ex) {
+ Assert.assertTrue(Optional.of(ex)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause)
+ .filter(x -> x instanceof ConstraintViolationException)
+ .isPresent());
+ } finally {
+ entityManager.getTransaction().rollback();
+ }
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueConstraintIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueConstraintIntegrationTest.java
new file mode 100644
index 0000000000..f12313724e
--- /dev/null
+++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueConstraintIntegrationTest.java
@@ -0,0 +1,116 @@
+package com.baeldung.jpa.uniqueconstraints;
+
+import java.util.Optional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.hibernate.exception.ConstraintViolationException;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class UniqueConstraintIntegrationTest {
+ private static EntityManagerFactory factory;
+ private static EntityManager entityManager;
+
+ @BeforeAll
+ public static void setup() {
+ factory = Persistence.createEntityManagerFactory("jpa-unique-constraints");
+ entityManager = factory.createEntityManager();
+ }
+
+ @Test
+ public void whenPersistPersonWithSameNumberAndStatus_thenConstraintViolationException() {
+ Person person1 = new Person();
+ person1.setPersonNumber(12345L);
+ person1.setIsActive(Boolean.TRUE);
+
+ Person person2 = new Person();
+ person2.setPersonNumber(12345L);
+ person2.setIsActive(Boolean.TRUE);
+
+ entityManager.getTransaction().begin();
+ entityManager.persist(person1);
+ entityManager.getTransaction().commit();
+
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(person2);
+ entityManager.getTransaction().commit();
+ Assert.fail("Should raise an exception - unique key violation");
+ } catch (Exception ex) {
+ Assert.assertTrue(Optional.of(ex)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause)
+ .filter(x -> x instanceof ConstraintViolationException)
+ .isPresent());
+ } finally {
+ entityManager.getTransaction().rollback();
+ }
+ }
+
+ @Test
+ public void whenPersistPersonWithSameSCodeAndDecode_thenConstraintViolationException() {
+ Person person1 = new Person();
+ person1.setDcode("Sec1");
+ person1.setScode("Axybg356");
+
+ Person person2 = new Person();
+ person2.setDcode("Sec1");
+ person2.setScode("Axybg356");
+
+ entityManager.getTransaction().begin();
+ entityManager.persist(person1);
+ entityManager.getTransaction().commit();
+
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(person2);
+ entityManager.getTransaction().commit();
+ Assert.fail("Should raise an exception - unique key violation");
+ } catch (Exception ex) {
+ Assert.assertTrue(Optional.of(ex)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause)
+ .filter(x -> x instanceof ConstraintViolationException)
+ .isPresent());
+ } finally {
+ entityManager.getTransaction().rollback();
+ }
+ }
+
+ @Test
+ public void whenPersistPersonWithSameNumberAndAddress_thenConstraintViolationException() {
+ Address address1 = new Address();
+ address1.setStreetAddress("40 Street");
+
+ Person person1 = new Person();
+ person1.setPersonNumber(54321L);
+ person1.setAddress(address1);
+
+ Person person2 = new Person();
+ person2.setPersonNumber(99999L);
+ person2.setAddress(address1);
+
+ entityManager.getTransaction().begin();
+ entityManager.persist(person1);
+ entityManager.getTransaction().commit();
+
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(person2);
+ entityManager.getTransaction().commit();
+ Assert.fail("Should raise an exception - unique key violation");
+ } catch (Exception ex) {
+ Assert.assertTrue(Optional.of(ex)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause)
+ .filter(x -> x instanceof ConstraintViolationException)
+ .isPresent());
+ } finally {
+ entityManager.getTransaction().rollback();
+ }
+ }
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/com/baeldung/reactor/mapping/MappingUnitTest.java b/reactor-core/src/test/java/com/baeldung/reactor/mapping/MappingUnitTest.java
new file mode 100644
index 0000000000..137bcbe021
--- /dev/null
+++ b/reactor-core/src/test/java/com/baeldung/reactor/mapping/MappingUnitTest.java
@@ -0,0 +1,37 @@
+package com.baeldung.reactor.mapping;
+
+import org.junit.Test;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MappingUnitTest {
+ @Test
+ public void givenInputStream_whenCallingTheMapOperator_thenItemsAreTransformed() {
+ Function mapper = String::toUpperCase;
+ Flux inFlux = Flux.just("baeldung", ".", "com");
+ Flux outFlux = inFlux.map(mapper);
+
+ StepVerifier.create(outFlux)
+ .expectNext("BAELDUNG", ".", "COM")
+ .expectComplete()
+ .verify();
+ }
+
+ @Test
+ public void givenInputStream_whenCallingTheFlatMapOperator_thenItemsAreFlatten() {
+ Function> mapper = s -> Flux.just(s.toUpperCase().split(""));
+ Flux inFlux = Flux.just("baeldung", ".", "com");
+ Flux outFlux = inFlux.flatMap(mapper);
+
+ List output = new ArrayList<>();
+ outFlux.subscribe(output::add);
+ assertThat(output).containsExactlyInAnyOrder("B", "A", "E", "L", "D", "U", "N", "G", ".", "C", "O", "M");
+ }
+}