diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
index 33916b6..3f547fc 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
@@ -47,7 +47,7 @@
* the reader will begin again at the same number item it left off at.
*
* This implementation is thread-safe between calls to {@link #open(ExecutionContext)},
- * but remember to set saveState to false if used in a
+ * but remember to {@link #setSaveState(boolean)} to {@code false} if used in a
* multi-threaded environment (no restart available).
*
* @author Stefano Cordio
@@ -55,9 +55,15 @@
*/
public class NotionDatabaseItemReader extends AbstractPaginatedDataItemReader {
- private static final int DEFAULT_PAGE_SIZE = 100;
+ /**
+ * Default number of items to be read with each page.
+ */
+ public static final int DEFAULT_PAGE_SIZE = 100;
- private static final String DEFAULT_BASE_URL = "https://api.notion.com/v1";
+ /**
+ * Default URL of the Notion API.
+ */
+ public static final String DEFAULT_BASE_URL = "https://api.notion.com/v1";
private final String token;
@@ -85,9 +91,12 @@ public class NotionDatabaseItemReader extends AbstractPaginatedDataItemReader
* of a Notion item into a Java object
*/
public NotionDatabaseItemReader(String token, String databaseId, PropertyMapper propertyMapper) {
- this.token = Objects.requireNonNull(token);
- this.databaseId = Objects.requireNonNull(databaseId);
- this.propertyMapper = Objects.requireNonNull(propertyMapper);
+ Assert.notNull(token, "token is required");
+ Assert.notNull(databaseId, "databaseId is required");
+ Assert.notNull(propertyMapper, "propertyMapper is required");
+ this.token = token;
+ this.databaseId = databaseId;
+ this.propertyMapper = propertyMapper;
this.pageSize = DEFAULT_PAGE_SIZE;
}
@@ -108,7 +117,7 @@ public void setBaseUrl(String baseUrl) {
* {@link Filter} condition to limit the returned items.
*
* If no filter is provided, all the items in the database will be returned.
- * @param filter the {@link Filter} conditions
+ * @param filter the {@link Filter} condition
* @see Filter#where()
* @see Filter#where(Filter)
*/
@@ -142,9 +151,6 @@ public void setPageSize(int pageSize) {
super.setPageSize(pageSize);
}
- /**
- * {@inheritDoc}
- */
@Override
protected void doOpen() {
RestClient restClient = RestClient.builder()
@@ -160,9 +166,6 @@ protected void doOpen() {
hasMore = true;
}
- /**
- * {@inheritDoc}
- */
@Override
protected Iterator doPageRead() {
if (!hasMore) {
@@ -205,17 +208,11 @@ private static String getPlainText(List texts) {
return texts.isEmpty() ? "" : texts.get(0).plainText();
}
- /**
- * {@inheritDoc}
- */
@Override
protected void doClose() {
hasMore = false;
}
- /**
- * {@inheritDoc}
- */
@Override
protected void jumpToItem(int itemIndex) throws Exception {
for (int i = 0; i < itemIndex; i++) {
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/builder/NotionDatabaseItemReaderBuilder.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/builder/NotionDatabaseItemReaderBuilder.java
new file mode 100644
index 0000000..6674831
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/builder/NotionDatabaseItemReaderBuilder.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2024-2026 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.
+ */
+package org.springframework.batch.extensions.notion.builder;
+
+import org.jspecify.annotations.Nullable;
+import org.springframework.batch.extensions.notion.Filter;
+import org.springframework.batch.extensions.notion.NotionDatabaseItemReader;
+import org.springframework.batch.extensions.notion.Sort;
+import org.springframework.batch.extensions.notion.mapping.PropertyMapper;
+import org.springframework.batch.infrastructure.item.ExecutionContext;
+import org.springframework.util.Assert;
+
+/**
+ * A builder for the {@link NotionDatabaseItemReader}.
+ *
+ * @author Jaeung Ha
+ * @param Type of item to be read
+ * @see NotionDatabaseItemReader
+ * @since 0.2.0
+ */
+public class NotionDatabaseItemReaderBuilder {
+
+ private @Nullable String token;
+
+ private @Nullable String databaseId;
+
+ private @Nullable PropertyMapper propertyMapper;
+
+ private @Nullable String baseUrl;
+
+ private @Nullable Filter filter;
+
+ private @Nullable String name;
+
+ private Sort[] sorts = new Sort[0];
+
+ private int pageSize = NotionDatabaseItemReader.DEFAULT_PAGE_SIZE;
+
+ private boolean saveState = true;
+
+ private int maxItemCount = Integer.MAX_VALUE;
+
+ private int currentItemCount = 0;
+
+ /**
+ * Create a new {@link NotionDatabaseItemReaderBuilder}.
+ */
+ public NotionDatabaseItemReaderBuilder() {
+ }
+
+ /**
+ * The Notion integration token.
+ * @param token the token
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#NotionDatabaseItemReader(String, String,
+ * PropertyMapper)
+ */
+ public NotionDatabaseItemReaderBuilder token(String token) {
+ this.token = token;
+ return this;
+ }
+
+ /**
+ * The UUID of the database to read from.
+ * @param databaseId the database UUID
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#NotionDatabaseItemReader(String, String,
+ * PropertyMapper)
+ */
+ public NotionDatabaseItemReaderBuilder databaseId(String databaseId) {
+ this.databaseId = databaseId;
+ return this;
+ }
+
+ /**
+ * The {@link PropertyMapper} responsible for mapping properties of a Notion item into
+ * a Java object.
+ * @param propertyMapper the property mapper
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#NotionDatabaseItemReader(String, String,
+ * PropertyMapper)
+ */
+ public NotionDatabaseItemReaderBuilder propertyMapper(PropertyMapper propertyMapper) {
+ this.propertyMapper = propertyMapper;
+ return this;
+ }
+
+ /**
+ * The base URL of the Notion API.
+ *
+ * Defaults to {@value NotionDatabaseItemReader#DEFAULT_BASE_URL}.
+ *
+ * A custom value can be provided for testing purposes (e.g., the URL of a WireMock
+ * server).
+ * @param baseUrl the base URL
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setBaseUrl(String)
+ */
+ public NotionDatabaseItemReaderBuilder baseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ return this;
+ }
+
+ /**
+ * {@link Filter} condition to limit the returned items.
+ *
+ * If no filter is provided, all the items in the database will be returned.
+ * @param filter the filter
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setFilter(Filter)
+ */
+ public NotionDatabaseItemReaderBuilder filter(Filter filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ /**
+ * {@link Sort} conditions to order the returned items.
+ *
+ * Each condition is applied following the declaration order, i.e., earlier sorts take
+ * precedence over later ones.
+ * @param sorts the {@link Sort} conditions
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setSorts(Sort...)
+ */
+ public NotionDatabaseItemReaderBuilder sorts(Sort... sorts) {
+ this.sorts = sorts;
+ return this;
+ }
+
+ /**
+ * The number of items to be read with each page.
+ *
+ * Defaults to {@value NotionDatabaseItemReader#DEFAULT_PAGE_SIZE}.
+ * @param pageSize the page size
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setPageSize(int)
+ */
+ public NotionDatabaseItemReaderBuilder pageSize(int pageSize) {
+ this.pageSize = pageSize;
+ return this;
+ }
+
+ /**
+ * Sets the flag that determines whether to save the state of the reader for restarts.
+ * @param saveState the save state flag
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setSaveState(boolean)
+ */
+ public NotionDatabaseItemReaderBuilder saveState(boolean saveState) {
+ this.saveState = saveState;
+ return this;
+ }
+
+ /**
+ * The name of the component which will be used as a stem for keys in the
+ * {@link ExecutionContext}.
+ * @param name the name for the component
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setName(String)
+ */
+ public NotionDatabaseItemReaderBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * The maximum index of the items to be read.
+ * @param maxItemCount the maximum item count
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setMaxItemCount(int)
+ */
+ public NotionDatabaseItemReaderBuilder maxItemCount(int maxItemCount) {
+ this.maxItemCount = maxItemCount;
+ return this;
+ }
+
+ /**
+ * The index of the item to start reading from.
+ * @param currentItemCount the current item count
+ * @return the current instance of the builder
+ * @see NotionDatabaseItemReader#setCurrentItemCount(int)
+ */
+ public NotionDatabaseItemReaderBuilder currentItemCount(int currentItemCount) {
+ this.currentItemCount = currentItemCount;
+ return this;
+ }
+
+ /**
+ * Builds the {@link NotionDatabaseItemReader}.
+ * @return the built reader
+ */
+ public NotionDatabaseItemReader build() {
+ if (this.saveState && this.name != null) {
+ Assert.hasText(this.name, "A name is required when saveState is set to true.");
+ }
+
+ if (token == null || databaseId == null || propertyMapper == null) {
+ throw new IllegalArgumentException("token, databaseId, and propertyMapper must not be null");
+ }
+ NotionDatabaseItemReader reader = new NotionDatabaseItemReader<>(token, databaseId, propertyMapper);
+
+ reader.setSaveState(saveState);
+ if (baseUrl != null) {
+ reader.setBaseUrl(baseUrl);
+ }
+ if (name != null) {
+ reader.setName(name);
+ }
+ if (filter != null) {
+ reader.setFilter(filter);
+ }
+ reader.setSorts(sorts);
+ reader.setPageSize(pageSize);
+ reader.setMaxItemCount(maxItemCount);
+ reader.setCurrentItemCount(currentItemCount);
+
+ return reader;
+ }
+
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/builder/package-info.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/builder/package-info.java
new file mode 100644
index 0000000..2083c3f
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/builder/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024-2026 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.
+ */
+
+/**
+ * Builder for Notion item reader.
+ */
+@NullMarked
+package org.springframework.batch.extensions.notion.builder;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/mapping/package-info.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/mapping/package-info.java
index a76a140..445c80c 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/mapping/package-info.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/mapping/package-info.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
* Infrastructure to map the properties of a Notion item into a Java object.
*/
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/package-info.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/package-info.java
index b8631e4..77611ab 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/package-info.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/package-info.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
* Spring Batch extension for Notion.
*/
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/builder/NotionDatabaseItemReaderBuilderTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/builder/NotionDatabaseItemReaderBuilderTests.java
new file mode 100644
index 0000000..b75ba0f
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/builder/NotionDatabaseItemReaderBuilderTests.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2024-2026 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.
+ */
+package org.springframework.batch.extensions.notion.builder;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.batch.extensions.notion.Filter;
+import org.springframework.batch.extensions.notion.NotionDatabaseItemReader;
+import org.springframework.batch.extensions.notion.Sort;
+import org.springframework.batch.extensions.notion.mapping.PropertyMapper;
+
+import static org.assertj.core.api.BDDAssertions.catchException;
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.springframework.util.ClassUtils.getShortName;
+
+/**
+ * @author Jaeung Ha
+ */
+class NotionDatabaseItemReaderBuilderTests {
+
+ @Test
+ void should_succeed() {
+ // GIVEN
+ String token = "TOKEN";
+ String databaseId = "DATABASE ID";
+ PropertyMapper propertyMapper = properties -> "PROPERTY";
+ Filter filter = Filter.where().checkbox("IsActive").isEqualTo(true);
+ String name = "NAME";
+ Sort[] sorts = new Sort[0];
+ int pageSize = 50;
+ boolean saveState = true;
+ int maxItemCount = 1000;
+ int currentItemCount = 10;
+ String baseUrl = "https://example.com";
+
+ NotionDatabaseItemReaderBuilder underTest = new NotionDatabaseItemReaderBuilder() //
+ .token(token)
+ .databaseId(databaseId)
+ .propertyMapper(propertyMapper)
+ .filter(filter)
+ .name(name)
+ .sorts(sorts)
+ .pageSize(pageSize)
+ .saveState(saveState)
+ .maxItemCount(maxItemCount)
+ .currentItemCount(currentItemCount)
+ .baseUrl(baseUrl);
+
+ // WHEN
+ NotionDatabaseItemReader reader = underTest.build();
+
+ // THEN
+ then(reader).extracting("token").isEqualTo(token);
+ then(reader).extracting("databaseId").isEqualTo(databaseId);
+ then(reader).extracting("propertyMapper").isEqualTo(propertyMapper);
+ then(reader).extracting("filter").isEqualTo(filter);
+ then(reader).extracting("sorts").isEqualTo(sorts);
+ then(reader).extracting("pageSize").isEqualTo(pageSize);
+ then(reader).extracting("saveState").isEqualTo(saveState);
+ then(reader).extracting("name").isEqualTo(name);
+ then(reader).extracting("maxItemCount").isEqualTo(maxItemCount);
+ then(reader).extracting("currentItemCount").isEqualTo(currentItemCount);
+ then(reader).extracting("baseUrl").isEqualTo(baseUrl);
+ }
+
+ @Test
+ void should_fail_when_token_is_null() {
+ // GIVEN
+ NotionDatabaseItemReaderBuilder