diff --git a/google/cloud/bigquery_storage_v1/reader.py b/google/cloud/bigquery_storage_v1/reader.py
index 9b6e3e6..09b0d99 100644
--- a/google/cloud/bigquery_storage_v1/reader.py
+++ b/google/cloud/bigquery_storage_v1/reader.py
@@ -287,7 +287,13 @@ class ReadRowsIterable(object):
         record_batches = []
         for page in self.pages:
             record_batches.append(page.to_arrow())
-        return pyarrow.Table.from_batches(record_batches)
+
+        if record_batches:
+            return pyarrow.Table.from_batches(record_batches)
+
+        # No data, return an empty Table.
+        self._stream_parser._parse_arrow_schema()
+        return pyarrow.Table.from_batches([], schema=self._stream_parser._schema)
 
     def to_dataframe(self, dtypes=None):
         """Create a :class:`pandas.DataFrame` of all rows in the stream.
@@ -323,6 +329,7 @@ class ReadRowsIterable(object):
         # rarely no-copy, whereas pyarrow.Table.from_batches + to_pandas is
         # usually no-copy.
         schema_type = self._read_session.WhichOneof("schema")
+
         if schema_type == "arrow_schema":
             record_batch = self.to_arrow()
             df = record_batch.to_pandas()
@@ -330,10 +337,58 @@ class ReadRowsIterable(object):
                 df[column] = pandas.Series(df[column], dtype=dtypes[column])
             return df
 
-        frames = []
-        for page in self.pages:
-            frames.append(page.to_dataframe(dtypes=dtypes))
-        return pandas.concat(frames)
+        frames = [page.to_dataframe(dtypes=dtypes) for page in self.pages]
+
+        if frames:
+            return pandas.concat(frames)
+
+        # No data, construct an empty dataframe with columns matching the schema.
+        # The result should be consistent with what an empty ARROW stream would produce.
+        self._stream_parser._parse_avro_schema()
+        schema = self._stream_parser._avro_schema_json
+
+        column_dtypes = self._dtypes_from_avro(schema["fields"])
+        column_dtypes.update(dtypes)
+
+        df = pandas.DataFrame(columns=column_dtypes.keys())
+        for column in df:
+            df[column] = pandas.Series([], dtype=column_dtypes[column])
+
+        return df
+
+    def _dtypes_from_avro(self, avro_fields):
+        """Determine Pandas dtypes for columns in Avro schema.
+
+        Args:
+            avro_fields (Iterable[Mapping[str, Any]]):
+                Avro fields' metadata.
+
+        Returns:
+            colelctions.OrderedDict[str, str]:
+                Column names with their corresponding Pandas dtypes.
+        """
+        result = collections.OrderedDict()
+
+        type_map = {"long": "int64", "double": "float64", "boolean": "bool"}
+
+        for field_info in avro_fields:
+            # If a type is an union of multiple types, pick the first type
+            # that is not "null".
+            if isinstance(field_info["type"], list):
+                type_info = next(item for item in field_info["type"] if item != "null")
+
+            if isinstance(type_info, six.string_types):
+                field_dtype = type_map.get(type_info, "object")
+            else:
+                logical_type = type_info.get("logicalType")
+                if logical_type == "timestamp-micros":
+                    field_dtype = "datetime64[ns, UTC]"
+                else:
+                    field_dtype = "object"
+
+            result[field_info["name"]] = field_dtype
+
+        return result
 
 
 class ReadRowsPage(object):

ValueError: No objects to concatenate in to_dataframe method
If you have a small query result set  and many streams, then often one of the reading streams doesn't get any pages to read (probably other streams get to read all the data before ??)

Pandas doesn't allow `concat` on empty list. `return pandas.concat(frames)`

In this case the following exception occurs 

```
File "***/lib/python3.7/site-packages/google/cloud/bigquery_storage_v1/reader.py", line 236, in to_dataframe
    return self.rows(read_session).to_dataframe(dtypes=dtypes)

File "***/lib/python3.7/site-packages/google/cloud/bigquery_storage_v1/reader.py", line 336, in to_dataframe
    return pandas.concat(frames)

File "***/lib/python3.7/site-packages/pandas/core/reshape/concat.py", line 255, in concat
    sort=sort,

File "***/lib/python3.7/site-packages/pandas/core/reshape/concat.py", line 304, in __init__
    raise ValueError("No objects to concatenate")

ValueError: No objects to concatenate
```


diff --git a/tests/unit/test_reader_v1.py b/tests/unit/test_reader_v1.py
index 24e5dd4..febc872 100644
--- a/tests/unit/test_reader_v1.py
+++ b/tests/unit/test_reader_v1.py
@@ -645,6 +645,92 @@ def test_to_dataframe_w_dtypes_arrow(class_under_test):
     )
 
 
+def test_to_dataframe_empty_w_scalars_avro(class_under_test):
+    avro_schema = _bq_to_avro_schema(SCALAR_COLUMNS)
+    read_session = _generate_avro_read_session(avro_schema)
+    avro_blocks = _bq_to_avro_blocks([], avro_schema)
+    reader = class_under_test(avro_blocks, mock_client, "", 0, {})
+
+    got = reader.to_dataframe(read_session)
+
+    expected = pandas.DataFrame(columns=SCALAR_COLUMN_NAMES)
+    expected["int_col"] = expected["int_col"].astype("int64")
+    expected["float_col"] = expected["float_col"].astype("float64")
+    expected["bool_col"] = expected["bool_col"].astype("bool")
+    expected["ts_col"] = expected["ts_col"].astype("datetime64[ns, UTC]")
+
+    pandas.testing.assert_frame_equal(
+        got.reset_index(drop=True),  # reset_index to ignore row labels
+        expected.reset_index(drop=True),
+    )
+
+
+def test_to_dataframe_empty_w_scalars_arrow(class_under_test):
+    arrow_schema = _bq_to_arrow_schema(SCALAR_COLUMNS)
+    read_session = _generate_arrow_read_session(arrow_schema)
+    arrow_batches = _bq_to_arrow_batches([], arrow_schema)
+    reader = class_under_test(arrow_batches, mock_client, "", 0, {})
+
+    got = reader.to_dataframe(read_session)
+
+    expected = pandas.DataFrame([], columns=SCALAR_COLUMN_NAMES)
+    expected["int_col"] = expected["int_col"].astype("int64")
+    expected["float_col"] = expected["float_col"].astype("float64")
+    expected["bool_col"] = expected["bool_col"].astype("bool")
+    expected["ts_col"] = expected["ts_col"].astype("datetime64[ns, UTC]")
+
+    pandas.testing.assert_frame_equal(
+        got.reset_index(drop=True),  # reset_index to ignore row labels
+        expected.reset_index(drop=True),
+    )
+
+
+def test_to_dataframe_empty_w_dtypes_avro(class_under_test, mock_client):
+    avro_schema = _bq_to_avro_schema(
+        [
+            {"name": "bigfloat", "type": "float64"},
+            {"name": "lilfloat", "type": "float64"},
+        ]
+    )
+    read_session = _generate_avro_read_session(avro_schema)
+    avro_blocks = _bq_to_avro_blocks([], avro_schema)
+    reader = class_under_test(avro_blocks, mock_client, "", 0, {})
+
+    got = reader.to_dataframe(read_session, dtypes={"lilfloat": "float16"})
+
+    expected = pandas.DataFrame([], columns=["bigfloat", "lilfloat"])
+    expected["bigfloat"] = expected["bigfloat"].astype("float64")
+    expected["lilfloat"] = expected["lilfloat"].astype("float16")
+
+    pandas.testing.assert_frame_equal(
+        got.reset_index(drop=True),  # reset_index to ignore row labels
+        expected.reset_index(drop=True),
+    )
+
+
+def test_to_dataframe_empty_w_dtypes_arrow(class_under_test, mock_client):
+    arrow_schema = _bq_to_arrow_schema(
+        [
+            {"name": "bigfloat", "type": "float64"},
+            {"name": "lilfloat", "type": "float64"},
+        ]
+    )
+    read_session = _generate_arrow_read_session(arrow_schema)
+    arrow_batches = _bq_to_arrow_batches([], arrow_schema)
+    reader = class_under_test(arrow_batches, mock_client, "", 0, {})
+
+    got = reader.to_dataframe(read_session, dtypes={"lilfloat": "float16"})
+
+    expected = pandas.DataFrame([], columns=["bigfloat", "lilfloat"])
+    expected["bigfloat"] = expected["bigfloat"].astype("float64")
+    expected["lilfloat"] = expected["lilfloat"].astype("float16")
+
+    pandas.testing.assert_frame_equal(
+        got.reset_index(drop=True),  # reset_index to ignore row labels
+        expected.reset_index(drop=True),
+    )
+
+
 def test_to_dataframe_by_page(class_under_test, mock_client):
     bq_columns = [
         {"name": "int_col", "type": "int64"},

pip install -e .[fastavro,pandas,pyarrow]
parse_log_pytest
pytest
pytest pytest-cov mock coverage
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
cachetools==5.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
coverage==7.2.7
fastavro==1.8.0
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
google-api-core==1.34.1
google-auth==2.38.0
-e git+https://github.com/googleapis/python-bigquery-storage.git@77b373b2cc87bc6ed1c8476117f258b4c0d242f2#egg=google_cloud_bigquery_storage
googleapis-common-protos==1.69.2
grpcio==1.62.3
grpcio-status==1.48.2
idna==3.10
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
mock==5.2.0
numpy==1.21.6
packaging @ file:///croot/packaging_1671697413597/work
pandas==1.3.5
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
protobuf==3.20.3
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyarrow==12.0.1
pyasn1==0.5.1
pyasn1-modules==0.3.0
pytest==7.1.2
pytest-cov==4.1.0
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.31.0
rsa==4.9
six==1.17.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
urllib3==2.0.7
zipp @ file:///croot/zipp_1672387121353/work

name: python-bigquery-storage
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==5.5.2
      - charset-normalizer==3.4.1
      - coverage==7.2.7
      - fastavro==1.8.0
      - google-api-core==1.34.1
      - google-auth==2.38.0
      - googleapis-common-protos==1.69.2
      - grpcio==1.62.3
      - grpcio-status==1.48.2
      - idna==3.10
      - mock==5.2.0
      - numpy==1.21.6
      - pandas==1.3.5
      - protobuf==3.20.3
      - pyarrow==12.0.1
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pytest-cov==4.1.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.31.0
      - rsa==4.9
      - six==1.17.0
      - urllib3==2.0.7
prefix: /opt/conda/envs/python-bigquery-storage

tests/unit/test_reader_v1.py::test_to_dataframe_empty_w_scalars_avro tests/unit/test_reader_v1.py::test_to_dataframe_empty_w_dtypes_avro
tests/unit/test_reader_v1.py::test_rows_w_scalars_arrow tests/unit/test_reader_v1.py::test_to_arrow_w_scalars_arrow tests/unit/test_reader_v1.py::test_to_dataframe_w_scalars_arrow tests/unit/test_reader_v1.py::test_to_dataframe_w_dtypes_arrow tests/unit/test_reader_v1.py::test_to_dataframe_empty_w_scalars_arrow tests/unit/test_reader_v1.py::test_to_dataframe_empty_w_dtypes_arrow tests/unit/test_reader_v1.py::test_to_dataframe_by_page_arrow
tests/unit/test_reader_v1.py::test_avro_rows_raises_import_error tests/unit/test_reader_v1.py::test_pyarrow_rows_raises_import_error tests/unit/test_reader_v1.py::test_rows_no_schema_set_raises_type_error tests/unit/test_reader_v1.py::test_rows_w_empty_stream tests/unit/test_reader_v1.py::test_rows_w_empty_stream_arrow tests/unit/test_reader_v1.py::test_rows_w_scalars tests/unit/test_reader_v1.py::test_rows_w_timeout tests/unit/test_reader_v1.py::test_rows_w_nonresumable_internal_error tests/unit/test_reader_v1.py::test_rows_w_reconnect tests/unit/test_reader_v1.py::test_rows_w_reconnect_by_page tests/unit/test_reader_v1.py::test_to_arrow_no_pyarrow_raises_import_error tests/unit/test_reader_v1.py::test_to_dataframe_no_pandas_raises_import_error tests/unit/test_reader_v1.py::test_to_dataframe_no_schema_set_raises_type_error tests/unit/test_reader_v1.py::test_to_dataframe_w_scalars tests/unit/test_reader_v1.py::test_to_dataframe_w_dtypes tests/unit/test_reader_v1.py::test_to_dataframe_by_page
google/cloud/bigquery_storage_v1/reader.py
google/cloud/bigquery_storage_v1/reader.py