[Canonical page](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/resource-load/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Resource load

This guide shows how to measure how busy each resource is over a date range, which is the data behind a resource histogram. It covers the profile query, how capacity follows resource calendars, which tasks fill each bucket, and how to get the load for dates you haven't committed yet.

## How a load profile works

[`Project#loadProfile`](<https://docs.dhtmlx.com/planning-engine/java/api/project-load-profile.md>) reads the solved baseline and cuts the date range into **buckets** of an hour or a day. For every resource it reports each bucket's `load` and `capacity`. The call is synchronous and read-only: it never schedules and never changes the project.

Three types are involved:

- A [`ProfileQuery`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/profile-query.md>) says what to measure: the range `from`..`to`, which `resources`, the bucket size (`basis`) and whether to list `tasks`. `new ProfileQuery(from, to)` gives you every resource, the default basis and no task lists. Override the rest with the fluent `withResources`, `withBasis` and `withTasks` setters.
- [`LoadProfileOptions`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile-options.md>) wraps the query and adds an optional `baselineId`. The usual call is `project.loadProfile(new LoadProfileOptions(query))`.
- The result is a [`LoadProfile`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md>): the `basis` it used and a `Vec<ResourceLoad>`, one [`ResourceLoad`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md#resourceload>) per resource. Each `ResourceLoad` has the `resource` id, its `capacityMode` and a list of [`LoadBucket`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md#loadbucket>) values with `start`, `load`, `capacity` and `tasks`.

These rules decide what ends up in the buckets:

- **Range.** A bucket is included when its start lies in `[from, to)`. A day bucket starts at midnight, so a `from` of Monday 12:00 skips Monday and starts on Tuesday. `to` is exclusive.
- **Dense output.** Every bucket in the range appears, including empty ones with load 0. That's why the range is capped at 100 000 buckets per resource, about 11 years at hour basis.
- **Basis.** With `basis` left `null`, the basis follows [`config.durationUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#durationunit>): [`Basis.DAY`](<https://docs.dhtmlx.com/planning-engine/java/api/data/basis.md>) for days, `Basis.HOUR` for hours and minutes. Set `basis` to override it.
- **Load units.** Load is in assignment `value` units, where 1 is 100 %. Nothing is converted to hours. An assignment that works in a bucket books its `value` there, so two full-time tasks on the same day give a load of 2.0.
- **Same load as leveling.** The profile uses the same sweep as the leveler and as the `overallocations` in [`ScheduleResult`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md>): only real work segments, each assignment on its own calendar, cut to its slice. A bucket overallocates when `load > capacity`.
- **Order.** Resources come back in input order, and so do the task ids in a bucket.

The profile always describes the accepted baseline, so the project has to be solved and clean. Failures throw unchecked exceptions:

| Failure | Exception |
|---|---|
| Project not solved yet, or edited with [`apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>) since the last solve | [`ProjectException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/project-exception.md>) with code [`NO_BASELINE`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/project-exception.md#projecterror>) |
| `baselineId` doesn't name the current baseline | `ProjectException` with code `STALE_BASELINE` |
| `to <= from`, or more than 100 000 buckets | [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>), path `to` |
| Unknown id in `resources` | `InputException`, path `resources` |

See [Handling errors](<https://docs.dhtmlx.com/planning-engine/java/error-handling.md>) for the exception types and the [`LoadProfile` reference](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md>) for every field.

## Querying a profile

Solve the project, build a [`ProfileQuery`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/profile-query.md>) and pass it to [`loadProfile`](<https://docs.dhtmlx.com/planning-engine/java/api/project-load-profile.md>).

```java
import org.dhtmlx.plen.*;
import java.util.List;

public class QueryProfile {
    static Moment at(int day, int hour) {
        return Moment.fromYmdHm(2026, 8, day, hour, 0);
    }

    static TaskData task(String id, Moment start, double duration) {
        return new TaskData(Id.str(id)).withStartDate(start).withDuration(duration);
    }

    static AssignmentData assign(String task, String resource, double value) {
        return new AssignmentData()
            .withId(Id.str(task + ":" + resource))
            .withTask(Id.str(task))
            .withResource(Id.str(resource))
            .withValue(value);
    }

    public static void main(String[] args) throws Exception {
        // 2026-08-10 is a Monday.
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                task("a", at(10, 8), 2.0),
                task("b", at(10, 8), 1.0),
                task("c", at(11, 8), 1.0),
                task("d", at(12, 8), 1.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev"))))
            .withAssignments(List.of(
                assign("a", "dev", 1.0),
                assign("b", "dev", 1.0),
                assign("c", "dev", 0.5),
                assign("d", "dev", 1.0)));

        Project project = new Project();
        project.solve(data, new SolveOptions());

        // Sunday 9 up to Friday 14; `to` is exclusive.
        ProfileQuery query = new ProfileQuery(at(9, 0), at(14, 0));
        LoadProfile profile = project.loadProfile(new LoadProfileOptions(query));

        System.out.println("basis: " + profile.basis);
        for (ResourceLoad row : profile.resources) {
            for (LoadBucket bucket : row.buckets) {
                System.out.println(row.resource + " " + bucket.start
                    + ": load " + bucket.load + " of " + bucket.capacity);
            }
        }
    }
}
```

The project uses day units, so the basis is `DAY` and you get five buckets, Sunday to Thursday. The loads are 0.0, 2.0 (`a` and `b`), 1.5 (`a` plus `c` at half time), 1.0 (`d`) and 0.0. Capacity is 1.0 in every bucket: `dev` has no `capacity` of its own, so it takes [`config.defaultResourceCapacity`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#defaultresourcecapacity>).

### Narrowing the query

Set `resources` to measure only some resources, and `basis` to change the bucket size. Both are fluent setters on [`ProfileQuery`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/profile-query.md>); `withResources` also takes a `List.of(...)`.

```java
import org.dhtmlx.plen.*;
import java.util.List;

public class NarrowQuery {
    static Moment at(int day, int hour) {
        return Moment.fromYmdHm(2026, 8, day, hour, 0);
    }

    static TaskData task(String id, Moment start, double duration) {
        return new TaskData(Id.str(id)).withStartDate(start).withDuration(duration);
    }

    static AssignmentData assign(String task, String resource) {
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str(resource))
            .withValue(1.0);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(task("a", at(10, 8), 2.0), task("b", at(10, 8), 1.0)))
            .withResources(List.of(
                new ResourceData().withId(Id.str("dev")),
                new ResourceData().withId(Id.str("qa"))))
            .withAssignments(List.of(assign("a", "dev"), assign("b", "dev")));

        Project project = new Project();
        project.solve(data, new SolveOptions());

        // Monday 07:00 to 10:00, hour buckets, `dev` only.
        ProfileQuery query = new ProfileQuery(at(10, 7), at(10, 10))
            .withResources(List.of(Id.str("dev")))
            .withBasis(Basis.HOUR);
        LoadProfile profile = project.loadProfile(new LoadProfileOptions(query));

        for (ResourceLoad row : profile.resources) {
            List<Double> loads = row.buckets.stream().map(b -> b.load).toList();
            System.out.println(row.resource + ": " + loads);
        }
    }
}
```

Only `dev` comes back, with three hour buckets. The 07:00 bucket is before work starts (0.0), and 08:00 and 09:00 carry both tasks (2.0 each). An id in `resources` that isn't in the project throws an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) at path `resources`.

### Pinning the profile to a baseline

A UI often fetches the profile some time after the solve. To make sure the profile matches the dates on screen, pass the token from the solve result as [`LoadProfileOptions.baselineId`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile-options.md#baselineid>). If the project was solved again in the meantime, the call throws a [`ProjectException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/project-exception.md>) with code `STALE_BASELINE` instead of returning load for other dates.

```java
import org.dhtmlx.plen.*;
import java.util.List;

public class PinnedProfile {
    static Moment at(int day, int hour) {
        return Moment.fromYmdHm(2026, 8, day, hour, 0);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                new TaskData(Id.str("a")).withStartDate(at(10, 8)).withDuration(2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev"))))
            .withAssignments(List.of(new AssignmentData()
                .withTask(Id.str("a"))
                .withResource(Id.str("dev"))
                .withValue(1.0)));

        Project project = new Project();
        ScheduleResult solved = project.solve(data, new SolveOptions());

        LoadProfileOptions options =
            new LoadProfileOptions(new ProfileQuery(at(10, 0), at(15, 0)))
                .withBaselineId(solved.baselineId);
        LoadProfile profile = project.loadProfile(options);
        System.out.println(profile.resources.size() + " resource(s) measured");
    }
}
```

Take the token from the latest solve result. Tokens are opaque, and the same data solved twice can get a different one, so don't store them or compare them across runs.

## Capacity modes

Capacity is the resource's `capacity`, or `config.defaultResourceCapacity` (1.0) when it's `null`. The `capacityMode` of each [`ResourceLoad`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md#resourceload>) tells you how it's spread over the buckets:

- [`CapacityMode.CALENDAR`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md#capacitymode>): the resource has its own calendar, [`config.calendarMode`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#calendarmode>) is `RESOURCE` (the default) and [`config.useCalendars`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#usecalendars>) is on. Buckets with working time on that calendar get the full capacity, all others get 0.
- `CapacityMode.FLAT`: every bucket gets the full capacity, weekends and nights included. This applies when the resource has no calendar, when `calendarMode` is a task mode (`TASK` or `TASK_INHERITED`), or when `useCalendars` is `false`.

In calendar mode an assignment always works on the resource's calendar, or on the overlap of the task's and the resource's calendars. So a bucket with capacity 0 never gets load, and a bucket overallocates in the profile exactly when leveling sees it as overallocated.

```java
import org.dhtmlx.plen.*;
import java.util.List;

public class CapacityModes {
    static Moment at(int day, int hour) {
        return Moment.fromYmdHm(2026, 8, day, hour, 0);
    }

    // Days run Sunday..Saturday.
    static CalendarData calendar(String id, boolean[] days, int startHour, int endHour) {
        return new CalendarData()
            .withId(Id.str(id))
            .withWorktime(new Worktime()
                .withHours(List.of(new TimeRange(startHour * 60, endHour * 60)))
                .withDays(days));
    }

    static ResourceData resource(String id, String calendar) {
        return new ResourceData()
            .withId(Id.str(id))
            .withCalendarId(calendar == null ? null : Id.str(calendar));
    }

    static final boolean[] MON_FRI = {false, true, true, true, true, true, false};
    static final boolean[] TUE_SAT = {false, false, true, true, true, true, true};

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                new TaskData(Id.str("a")).withStartDate(at(11, 8)).withDuration(2.0))) // Tuesday
            .withCalendars(List.of(
                calendar("global", MON_FRI, 8, 16),
                calendar("tue_sat", TUE_SAT, 8, 16),
                calendar("mornings", MON_FRI, 8, 12)))
            .withResources(List.of(
                resource("office", null),
                resource("shop", "tue_sat"),
                resource("part_time", "mornings")))
            .withAssignments(List.of(new AssignmentData()
                .withTask(Id.str("a"))
                .withResource(Id.str("shop"))
                .withValue(1.0)));

        Project project = new Project();
        project.solve(data, new SolveOptions());

        // One week in day buckets, Sunday 9 to Saturday 15.
        LoadProfile week = project.loadProfile(
            new LoadProfileOptions(new ProfileQuery(at(9, 0), at(16, 0))));
        for (ResourceLoad row : week.resources) {
            List<Double> capacity = row.buckets.stream().map(b -> b.capacity).toList();
            List<Double> load = row.buckets.stream().map(b -> b.load).toList();
            System.out.println(row.resource + " (" + row.capacityMode + "): capacity "
                + capacity + ", load " + load);
        }

        // Tuesday 06:00 to 18:00 in hour buckets, for the part-time resource.
        ProfileQuery day = new ProfileQuery(at(11, 6), at(11, 18))
            .withResources(List.of(Id.str("part_time")))
            .withBasis(Basis.HOUR);
        LoadProfile hours = project.loadProfile(new LoadProfileOptions(day));
        List<Double> capacity =
            hours.resources.get(0).buckets.stream().map(b -> b.capacity).toList();
        System.out.println("part_time hourly capacity: " + capacity);
    }
}
```

What the week shows:

- `office` has no calendar, so it's `FLAT` with capacity 1.0 on all seven days, Sunday and Saturday included.
- `shop` is `CALENDAR`. Its capacity is 0 on Sunday and Monday and 1.0 from Tuesday to Saturday. Task `a` runs Tuesday and Wednesday, so those two buckets have load 1.0.
- At hour basis, `part_time` has capacity only in the four hour buckets between 08:00 and 12:00. The other eight hours are 0.

Set `data.config.calendarMode = CalendarMode.TASK` (or `data.config.useCalendars = false`) and all three resources become `FLAT`, with capacity 1.0 in every bucket. See [Working time and calendars](<https://docs.dhtmlx.com/planning-engine/java/scheduling/working-time-calendars.md>) for calendar setup and the [`CapacityMode` reference](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md#capacitymode>).

## Task lists per bucket

A histogram usually needs to say *who* is overloading a resource. Set [`ProfileQuery.tasks`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/profile-query.md>) to fill [`LoadBucket.tasks`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile.md#loadbucket>):

| [`ProfileTasks`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/profile-tasks.md>) | `LoadBucket.tasks` |
|---|---|
| `NONE` (default) | `null` in every bucket |
| `OVER` | the ids in buckets where `load > capacity`, an empty `Vec` elsewhere |
| `ALL` | the ids of every task booked in the bucket |

```java
import org.dhtmlx.plen.*;
import java.util.List;

public class TaskLists {
    static Moment at(int day, int hour) {
        return Moment.fromYmdHm(2026, 8, day, hour, 0);
    }

    static TaskData task(String id, Moment start, double duration) {
        return new TaskData(Id.str(id)).withStartDate(start).withDuration(duration);
    }

    static AssignmentData assign(String task, double value) {
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str("dev"))
            .withValue(value);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                task("a", at(10, 8), 2.0),
                task("b", at(10, 8), 1.0),
                task("c", at(11, 8), 1.0),
                task("d", at(12, 8), 1.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev"))))
            .withAssignments(List.of(
                assign("a", 1.0),
                assign("b", 1.0),
                assign("c", 0.5),
                assign("d", 1.0)));

        Project project = new Project();
        project.solve(data, new SolveOptions());

        // Monday 10 to Thursday 13.
        ProfileQuery query = new ProfileQuery(at(10, 0), at(14, 0))
            .withTasks(ProfileTasks.OVER);
        LoadProfile profile = project.loadProfile(new LoadProfileOptions(query));

        for (LoadBucket bucket : profile.resources.get(0).buckets) {
            System.out.println(bucket.start + ": load " + bucket.load + ", tasks " + bucket.tasks);
        }
    }
}
```

With `OVER`, Monday lists `a` and `b`, and Tuesday lists `a` and `c`. Wednesday and Thursday aren't overloaded, so they get empty lists. Switch to `ProfileTasks.ALL` and Wednesday lists `d` too, because `ALL` reports every booked task regardless of capacity.

If you need only the overloaded periods, not the full histogram, skip the profile. Every [`ScheduleResult`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md>) of a project with assignments carries [`overallocations`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#overallocations>): per-resource windows with `peak`, `overload` and the contending `tasks`. See [Leveling resources](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md>) for how to remove the overload.

## Load of hypothetical dates

[`loadProfile`](<https://docs.dhtmlx.com/planning-engine/java/api/project-load-profile.md>) only reads the baseline. To see the load an edit *would* cause, set [`PreviewOptions.profile`](<https://docs.dhtmlx.com/planning-engine/java/api/project/preview-options.md#profile>) to a [`ProfileQuery`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/profile-query.md>) and call [`preview`](<https://docs.dhtmlx.com/planning-engine/java/api/project-preview.md>). The result's `profile` is computed on the previewed dates, and the project is left untouched.

```java
import org.dhtmlx.plen.*;
import java.util.List;

public class PreviewLoad {
    static Moment at(int day, int hour) {
        return Moment.fromYmdHm(2026, 8, day, hour, 0);
    }

    static TaskData task(String id, Moment start, double duration) {
        return new TaskData(Id.str(id)).withStartDate(start).withDuration(duration);
    }

    static AssignmentData assign(String task, String resource, double value) {
        return new AssignmentData()
            .withId(Id.str(task + ":" + resource))
            .withTask(Id.str(task))
            .withResource(Id.str(resource))
            .withValue(value);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                task("a", at(10, 8), 2.0),
                task("b", at(10, 8), 1.0),
                task("c", at(11, 8), 1.0)))
            .withResources(List.of(
                new ResourceData().withId(Id.str("dev")),
                new ResourceData().withId(Id.str("qa"))))
            .withAssignments(List.of(
                assign("a", "dev", 1.0),
                assign("b", "dev", 1.0),
                assign("c", "dev", 0.5)));

        Project project = new Project();
        project.solve(data, new SolveOptions());

        // What if `b` moves from `dev` to `qa`?
        SchedulingChanges changes = new SchedulingChanges();
        changes.assignments.update.add(
            new AssignmentUpdate(Id.str("b:dev")).withResource(Patch.of(Id.str("qa"))));

        PreviewOptions options = new PreviewOptions()
            .withProfile(new ProfileQuery(at(10, 0), at(12, 0)));
        ScheduleResult preview = project.preview(changes, options);

        for (ResourceLoad row : preview.profile.resources) {
            List<Double> loads = row.buckets.stream().map(b -> b.load).toList();
            System.out.println("previewed " + row.resource + ": " + loads);
        }

        // The baseline still has both tasks on `dev`.
        LoadProfile base = project.loadProfile(
            new LoadProfileOptions(new ProfileQuery(at(10, 0), at(12, 0))));
        List<Double> loads = base.resources.get(0).buckets.stream().map(b -> b.load).toList();
        System.out.println("baseline dev: " + loads);
    }
}
```

In the preview, `dev` drops to 1.0 on Monday and keeps 1.5 on Tuesday, and `qa` gets 1.0 on Monday. The baseline profile still shows 2.0 and 1.5 for `dev`. Without `PreviewOptions.profile`, `preview.profile` is `null`.

`PreviewOptions.profile` takes a bare `ProfileQuery`, not [`LoadProfileOptions`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/load-profile-options.md>), so it has no `baselineId` of its own. To guard a preview against a stale baseline, set [`PreviewOptions.baselineId`](<https://docs.dhtmlx.com/planning-engine/java/api/project/preview-options.md#baselineid>). An invalid query fails the whole preview with an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>).

To profile a multi-step edit or a leveling run, take a [`Project#copy()`](<https://docs.dhtmlx.com/planning-engine/java/api/project/project.md#copy>), edit or [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) the copy, and call `loadProfile` on it. The copy is cheap and the original stays as it was. See [Previewing changes](<https://docs.dhtmlx.com/planning-engine/java/live-projects/previewing-changes.md>) for both approaches.
