microg/GmsCore · error · IllegalStateException

DataSet has already been built.

Error message

DataSet has already been built.

What it means

Thrown by DataSet.Builder.add(DataPoint) after build() has already been called. The DataSet.Builder is single-use; once the DataSet is built, adding more points is rejected as an invalid state to prevent mutating an immutable, possibly already-consumed DataSet.

Source

Thrown at play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSet.java:178

     * Builder used to create new data sets.
     */
    public static class Builder {
        private final DataSet dataSet;
        private boolean built = false;

        Builder(DataSource dataSource) {
            this.dataSet = DataSet.create(dataSource);
        }

        /**
         * Adds a data point to this data set. The data points should be for the correct data type and data source, and should have its timestamp
         * already set.
         *
         * @throws IllegalArgumentException If dataPoint has the wrong {@link DataSource}, or contain invalid data.
         */
        @NonNull
        public Builder add(@NonNull DataPoint dataPoint) {
            if (built) throw new IllegalStateException("DataSet has already been built.");
            this.dataSet.add(dataPoint);
            return this;
        }

        /**
         * Adds a list of data points to this data set in bulk. All data points should be for the correct data type and data source, and should have their
         * timestamp already set.
         *
         * @throws IllegalArgumentException If the {@code dataPoints} have the wrong source, or contain invalid data.
         */
        @NonNull
        public Builder addAll(@NonNull Iterable<DataPoint> iterable) {
            if (built) throw new IllegalStateException("DataSet has already been built.");
            this.dataSet.addAll(iterable);
            return this;
        }

        /**

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Call add() for ALL data points before invoking build().
  2. Create a new DataSet.builder(dataSource) for each batch of points you want to commit separately.
  3. Restructure collection logic to accumulate points first, then build once at the end.
  4. Never cache or share a Builder instance after build() — treat it as consumed.

Example fix

// before
DataSet.Builder b = DataSet.builder(ds);
b.build();
b.add(laterPoint); // throws
// after
DataSet first = DataSet.builder(ds).add(p1).build();
DataSet second = DataSet.builder(ds).add(laterPoint).build();
Defensive patterns

Strategy: validation

Validate before calling

if (builderConsumed) { builder = DataSet.builder(dataSource); }

Type guard

boolean canAdd(DataSet.Builder b) { return !b.isBuilt(); } // track in your own wrapper

Try / catch

try { builder.add(point); } catch (IllegalStateException e) { builder = DataSet.builder(dataSource); builder.add(point); }

Prevention

When it happens

Trigger: Holding a DataSet.Builder reference, calling build(), then calling Builder.add(dataPoint) with a new point — often when streaming additional points after an initial build.

Common situations: Incremental data collection where code keeps appending to a builder after committing the DataSet; sharing a Builder across threads or callbacks where one path builds and another keeps adding.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/9e3e557e8539bb98. Report an issue: GitHub.