diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index 188ea132c6..20ee368827 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -88,6 +88,7 @@
spring-data-jpa-query
spring-data-jpa-query-2
spring-data-jpa-query-3
+ spring-data-jpa-query-4
spring-data-jpa-repo
spring-data-jpa-repo-2
spring-data-jpa-repo-4
diff --git a/persistence-modules/spring-data-jpa-query-4/pom.xml b/persistence-modules/spring-data-jpa-query-4/pom.xml
new file mode 100644
index 0000000000..3b843aed20
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+ spring-data-jpa-query-4
+ spring-data-jpa-query-4
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.h2database
+ h2
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+
+
+
+
+ 42.7.1
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/Product.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/Product.java
new file mode 100644
index 0000000000..ffe81fa13b
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/Product.java
@@ -0,0 +1,52 @@
+package com.baeldung.spring.data.jpa.queryjsonb;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Product {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+ @Column(columnDefinition = "jsonb")
+ private String attributes;
+
+ public Product() {
+ }
+
+ public Product(String name, String attributes) {
+ this.name = name;
+ this.attributes = attributes;
+ }
+
+ 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 getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(String attributes) {
+ this.attributes = attributes;
+ }
+
+}
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductApplication.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductApplication.java
new file mode 100644
index 0000000000..dc237ddf58
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.spring.data.jpa.queryjsonb;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ProductApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ProductApplication.class);
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductRepository.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductRepository.java
new file mode 100644
index 0000000000..8667afb3f4
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductRepository.java
@@ -0,0 +1,24 @@
+package com.baeldung.spring.data.jpa.queryjsonb;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor {
+ @Query(value = "SELECT * FROM product WHERE attributes ->> ?1 = ?2", nativeQuery = true)
+ List findByAttribute(String key, String value);
+
+ @Query(value = "SELECT * FROM product WHERE attributes -> ?1 ->> ?2 = ?3", nativeQuery = true)
+ List findByNestedAttribute(String key1, String key2, String value);
+
+ @Query(value = "SELECT * FROM product WHERE jsonb_extract_path_text(attributes, ?1) = ?2", nativeQuery = true)
+ List findByJsonPath(String path, String value);
+
+ @Query(value = "SELECT * FROM product WHERE jsonb_extract_path_text(attributes, ?1, ?2) = ?3", nativeQuery = true)
+ List findByNestedJsonPath(String key1, String key2, String value);
+
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductSpecification.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductSpecification.java
new file mode 100644
index 0000000000..65c9507cf0
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/queryjsonb/ProductSpecification.java
@@ -0,0 +1,22 @@
+package com.baeldung.spring.data.jpa.queryjsonb;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.springframework.data.jpa.domain.Specification;
+public class ProductSpecification implements Specification {
+
+ private final String key;
+ private final String value;
+
+ public ProductSpecification(String key, String value) {
+ this.key = key; this.value = value;
+ }
+
+ @Override
+ public Predicate toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder cb) {
+ return cb.equal(cb.function("jsonb_extract_path_text", String.class, root.get("attributes"), cb.literal(key)), value);
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties b/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties
new file mode 100644
index 0000000000..de0685b9bd
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties
@@ -0,0 +1,10 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
+spring.datasource.username=postgres
+spring.datasource.password=
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.hibernate.ddl-auto=create
+spring.jpa.show-sql=true
+
+logging.level.org.hibernate.SQL=DEBUG
+logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
+spring.jpa.properties.hibernate.format_sql=true
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/test/java/com/baeldung/spring/data/jpa/queryjsonb/ProductRepositoryIntegrationTest.java b/persistence-modules/spring-data-jpa-query-4/src/test/java/com/baeldung/spring/data/jpa/queryjsonb/ProductRepositoryIntegrationTest.java
new file mode 100644
index 0000000000..2385590d7f
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/test/java/com/baeldung/spring/data/jpa/queryjsonb/ProductRepositoryIntegrationTest.java
@@ -0,0 +1,77 @@
+package com.baeldung.spring.data.jpa.queryjsonb;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.transaction.Transactional;
+
+import org.junit.Before;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.test.annotation.Commit;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.transaction.annotation.Propagation;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@SpringBootTest
+@ActiveProfiles("test")
+@Sql(scripts = "/testdata.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+public class ProductRepositoryIntegrationTest {
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Test
+ void whenFindByAttribute_thenReturnTheObject() {
+ List redProducts = productRepository.findByAttribute("color", "red");
+
+ assertEquals(1, redProducts.size());
+ assertEquals("Laptop", redProducts.get(0).getName());
+ }
+
+ @Test
+ void whenFindByNestedAttribute_thenReturnTheObject() {
+ List electronicProducts = productRepository.findByNestedAttribute("details", "category", "electronics");
+
+ assertEquals(1, electronicProducts.size());
+ assertEquals("Headphones", electronicProducts.get(0)
+ .getName());
+ }
+
+ @Test
+ void whenFindByJsonPath_thenReturnTheObject() {
+ List redProducts = productRepository.findByJsonPath("color", "red");
+ assertEquals(1, redProducts.size());
+ assertEquals("Laptop", redProducts.get(0)
+ .getName());
+ }
+
+ @Test
+ void givenNestedJsonAttribute_whenFindByJsonPath_thenReturnTheObject() {
+ List electronicProducts = productRepository.findByNestedJsonPath("details", "category", "electronics");
+ assertEquals(1, electronicProducts.size());
+ assertEquals("Headphones", electronicProducts.get(0)
+ .getName());
+ }
+
+ @Test
+ void whenUsingJPASpecification_thenReturnTheObject() {
+ ProductSpecification spec = new ProductSpecification("color", "red");
+ Page redProducts = productRepository.findAll(spec, Pageable.unpaged());
+
+ assertEquals(1, redProducts.getContent()
+ .size());
+ assertEquals("Laptop", redProducts.getContent()
+ .get(0)
+ .getName());
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/test/resources/application-test.properties b/persistence-modules/spring-data-jpa-query-4/src/test/resources/application-test.properties
new file mode 100644
index 0000000000..af1e12cb9c
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/test/resources/application-test.properties
@@ -0,0 +1,10 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
+spring.datasource.username=postgres
+spring.datasource.password=
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.hibernate.ddl-auto=create
+spring.jpa.show-sql=true
+spring.jpa.properties.hibernate.format_sql=true
+
+logging.level.org.hibernate.SQL=DEBUG
+logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
diff --git a/persistence-modules/spring-data-jpa-query-4/src/test/resources/testdata.sql b/persistence-modules/spring-data-jpa-query-4/src/test/resources/testdata.sql
new file mode 100644
index 0000000000..71e9a3123d
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/test/resources/testdata.sql
@@ -0,0 +1,13 @@
+DELETE FROM product;
+
+INSERT INTO product (name, attributes)
+VALUES ('Laptop', '{"color": "red", "size": "15 inch"}');
+
+INSERT INTO product (name, attributes)
+VALUES ('Phone', '{"color": "blue", "size": "6 inch"}');
+
+INSERT INTO product (name, attributes)
+VALUES ('Headphones', '{"brand": "Sony", "details": {"category": "electronics", "model": "WH-1000XM4"}}');
+
+INSERT INTO product (name, attributes)
+VALUES ('Laptop', '{"brand": "Dell", "details": {"category": "computers", "model": "XPS 13"}}');
\ No newline at end of file