extends Serializable {
+ /**
+ * Counts the number of available items based on a pageable and a
+ * filter string. The pageable defines the paging of the items to
+ * fetch and the sorting and is provided although it is generally
+ * not needed for determining the number of items.
+ *
+ * @param pageable
+ * the pageable that defines which items to fetch and the
+ * sort order
+ * @param filterString
+ * the filter string provided by the ComboBox
+ * @return the number of available items
+ */
+ long count(PAGEABLE pageable, String filterString);
+ }
+ }
+
+ /**
+ * Supply items lazily with a callback from a backend based on a Spring Data
+ * Pageable. The component will automatically fetch more items and adjust
+ * its size until the backend runs out of items. Usage example:
+ *
+ * {@code comboBox.setItemsPageable((pageable, filterString) -> orderService.getOrders(pageable, filterString));}
+ *
+ * The returned data view object can be used for further configuration, or
+ * later on fetched with {@link #getLazyDataView()}. For using in-memory
+ * data, like {@link java.util.Collection}, use
+ * {@link HasListDataView#setItems(Collection)} instead.
+ *
+ * @param fetchCallback
+ * a function that returns a sorted list of items from the
+ * backend based on the given pageable
+ * @return a data view for further configuration
+ */
+ public ComboBoxLazyDataView setItemsPageable(
+ SpringData.FetchCallback fetchCallback) {
+ return setItems(
+ query -> handleSpringFetchCallback(query, fetchCallback));
+ }
+
+ /**
+ * Supply items lazily with callbacks: the first one fetches a list of items
+ * from a backend based on a Spring Data Pageable, the second provides the
+ * exact count of items in the backend. Use this in case getting the count
+ * is cheap and the user benefits from the component showing immediately the
+ * exact size. Usage example:
+ *
+ * {@code component.setItemsPageable(
+ * (pageable, filterString) -> orderService.getOrders(pageable, filterString),
+ * (pageable, filterString) -> orderService.countOrders(filterString));}
+ *
+ * The returned data view object can be used for further configuration, or
+ * later on fetched with {@link #getLazyDataView()}. For using in-memory
+ * data, like {@link java.util.Collection}, use
+ * {@link HasListDataView#setItems(Collection)} instead.
+ *
+ * @param fetchCallback
+ * a function that returns a sorted list of items from the
+ * backend based on the given pageable and filter string
+ * @param countCallback
+ * a function that returns the number of items in the back end
+ * based on the filter string
+ * @return LazyDataView instance for further configuration
+ */
+ public ComboBoxLazyDataView setItemsPageable(
+ SpringData.FetchCallback fetchCallback,
+ SpringData.CountCallback countCallback) {
+ return setItems(
+ query -> handleSpringFetchCallback(query, fetchCallback),
+ query -> handleSpringCountCallback(query, countCallback));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Stream handleSpringFetchCallback(
+ Query query,
+ SpringData.FetchCallback fetchCallback) {
+ PAGEABLE pageable = (PAGEABLE) VaadinSpringDataHelpers
+ .toSpringPageRequest(query);
+ List itemList = fetchCallback.fetch(pageable,
+ query.getFilter().orElse(""));
+ return itemList.stream();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static int handleSpringCountCallback(
+ Query, String> query,
+ SpringData.CountCallback countCallback) {
+ PAGEABLE pageable = (PAGEABLE) VaadinSpringDataHelpers
+ .toSpringPageRequest(query);
+ long count = countCallback.count(pageable,
+ query.getFilter().orElse(""));
+ if (count > Integer.MAX_VALUE) {
+ LoggerFactory.getLogger(ComboBoxBase.class).warn(
+ "The count of items in the backend ({}) exceeds the maximum supported by the ComboBox.",
+ count);
+ return Integer.MAX_VALUE;
+ }
+ return (int) count;
+ }
+
@Override
public ComboBoxLazyDataView setItems(
BackEndDataProvider dataProvider) {
diff --git a/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboBoxSpringDataTest.java b/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboBoxSpringDataTest.java
new file mode 100644
index 00000000000..8be7f513111
--- /dev/null
+++ b/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboBoxSpringDataTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * 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
+ *
+ * http://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 com.vaadin.flow.component.combobox;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ComboBoxSpringDataTest {
+ public static class Person implements Serializable {
+ private String name;
+ private final int born;
+
+ public Person(String name, int born) {
+ this.name = name;
+ this.born = born;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getBorn() {
+ return born;
+ }
+ }
+
+ private static final List data = List.of(new Person("John", 1293),
+ new Person("Jane", 1923), new Person("Homer", 1956));
+
+ @Test
+ public void setItemsPageableNoCountNoFilter() {
+ AtomicInteger pageSize = new AtomicInteger(-1);
+ AtomicInteger pageNumber = new AtomicInteger(-1);
+ ComboBox comboBox = new ComboBox<>();
+ comboBox.setItemsPageable((pageable, filterString) -> {
+ if (pageSize.get() != -1) {
+ throw new IllegalStateException(
+ "There should be only one call to the data provider");
+ }
+ pageSize.set(pageable.getPageSize());
+ pageNumber.set(pageable.getPageNumber());
+
+ return filteredData(filterString);
+ });
+
+ List items = comboBox.getLazyDataView().getItems().toList();
+ Assert.assertEquals(3, items.size());
+ Assert.assertEquals(0, pageNumber.get());
+ Assert.assertTrue(pageSize.get() > 0);
+ Assert.assertEquals("Homer", items.get(2).getName());
+ }
+
+ @Test
+ public void setItemsPageableNoCountFilter() {
+ AtomicInteger pageSize = new AtomicInteger(-1);
+ AtomicInteger pageNumber = new AtomicInteger(-1);
+ ComboBox comboBox = new ComboBox<>();
+ comboBox.setItemsPageable((pageable, filterString) -> {
+ if (pageSize.get() != -1) {
+ throw new IllegalStateException(
+ "There should be only one call to the data provider");
+ }
+ pageSize.set(pageable.getPageSize());
+ pageNumber.set(pageable.getPageNumber());
+
+ return filteredData(filterString);
+ });
+ comboBox.getDataController().setRequestedRange(0, 50, "J");
+
+ List items = comboBox.getLazyDataView().getItems().toList();
+ Assert.assertEquals(2, items.size());
+ Assert.assertEquals(0, pageNumber.get());
+ Assert.assertTrue(pageSize.get() > 0);
+ Assert.assertEquals("Jane", items.get(1).getName());
+ }
+
+ @Test
+ public void setItemsPageableWithCount() {
+ AtomicInteger pageSize = new AtomicInteger(-1);
+ AtomicInteger pageNumber = new AtomicInteger(-1);
+ ComboBox comboBox = new ComboBox<>();
+ comboBox.setItemsPageable((pageable, filterString) -> {
+ if (pageSize.get() != -1) {
+ throw new IllegalStateException(
+ "There should be only one call to the data provider");
+ }
+ pageSize.set(pageable.getPageSize());
+ pageNumber.set(pageable.getPageNumber());
+
+ return filteredData(filterString);
+ }, (pageable, filterString) -> 3L);
+
+ List items = comboBox.getLazyDataView().getItems().toList();
+
+ Assert.assertEquals(3, items.size());
+ Assert.assertEquals(0, pageNumber.get());
+ Assert.assertTrue(pageSize.get() > 0);
+ Assert.assertEquals("Jane", items.get(1).getName());
+ }
+
+ private static List filteredData(String filterString) {
+ return data.stream()
+ .filter(person -> person.getName().contains(filterString))
+ .toList();
+ }
+
+}
diff --git a/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboboxSerializableTest.java b/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboboxSerializableTest.java
index 5054f07b7b9..53415a86c4f 100644
--- a/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboboxSerializableTest.java
+++ b/vaadin-combo-box-flow-parent/vaadin-combo-box-flow/src/test/java/com/vaadin/flow/component/combobox/ComboboxSerializableTest.java
@@ -18,12 +18,20 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
+import java.util.stream.Stream;
import org.junit.Test;
import com.vaadin.flow.testutil.ClassesSerializableTest;
public class ComboboxSerializableTest extends ClassesSerializableTest {
+
+ @Override
+ protected Stream getExcludedPatterns() {
+ return Stream.concat(super.getExcludedPatterns(),
+ Stream.of("com\\.vaadin\\.flow\\.spring\\..*"));
+ }
+
@Test
public void setItems_callSetRequestedRange_comboBoxSerializable()
throws Throwable {