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


# Resources and assignments

This guide shows how to tell Planning Engine who does the work: resources with a capacity, assignments that book them on tasks, and slices that book only part of a task. It also shows how to read the overallocation windows that every scheduled plan reports.

## How resources work

Three records describe staffing. They all live in [`ProjectData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/project-data.md>) next to the tasks:

- A **resource** ([`ResourceData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/resource-data.md>)) is a person, a team or a machine. It has an `id`, an optional `capacity` and an optional `calendarId`.
- An **assignment** ([`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/assignment-data.md>)) books one resource on one task at a utilization rate, `value`. `1.0` means 100 %, so `0.5` is half of someone's time.
- A **slice** is an optional `offset` / `duration` pair on an assignment. With a slice, the assignment books only part of the task's work instead of all of it.

`capacity` uses the same units as `value`. A resource with `withCapacity(2.0)` can take two full-time assignments at once, like a team of two. A resource whose `capacity` is `null` gets [`config.defaultResourceCapacity`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#defaultresourcecapacity>), which is `1.0` by default.

To check load, the engine cuts the timeline into **buckets** of an hour or a day. The basis comes from [`config.durationUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#durationunit>): days for [`TimeUnit.DAY`](<https://docs.dhtmlx.com/planning-engine/java/api/data/time-unit.md>), hours for `HOUR` and `MINUTE`. The **load** of a resource in a bucket is the sum of the `value`s of its assignments whose task has working time in that bucket. A bucket **overallocates** when its load is strictly greater than the capacity. Two half-time assignments on a capacity of `1.0` are fine. A third one isn't.

`value` is a rate, not a number of hours. If a task has any working time inside a bucket, the assignment counts its full `value` there.

Assignments don't move tasks. Task dates come from links, constraints and calendars, and assignments only decide where load lands. Calendars are the one exception. In the default [`CalendarMode.RESOURCE`](<https://docs.dhtmlx.com/planning-engine/java/api/data/calendar-mode.md>), each assignment works on its resource's calendar, so a resource calendar can shift a task. See [Resource calendars](<https://docs.dhtmlx.com/planning-engine/java/scheduling/working-time-calendars.md#resource-calendars>).

When a project has assignments, every schedule fills [`ScheduleResult.overallocations`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#overallocations>). It holds one [`ResourceOverallocation`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/resource-overallocation.md>) per contended resource, each with a `Vec` of [`OverallocationWindow`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/resource-overallocation.md#overallocationwindow>)s. Overallocation is data, not an exception. Removing it is the job of [leveling](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md>).

[`Project#solve(data, …)`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>) and [`Engine.schedule`](<https://docs.dhtmlx.com/planning-engine/java/api/engine-schedule.md>) validate resources and assignments when you pass the data. These cases throw an unchecked [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) with the field path in `path`:

- a duplicate resource id (`resources[i].id`)
- a `capacity` that isn't finite or is below `0` (`resources[i].capacity`)
- a duplicate assignment id (`assignments[i].id`)
- an assignment `value` that isn't finite or is `0` or less (`assignments[i].value`)
- an assignment that names an unknown task or resource (`assignments[i].task`, `assignments[i].resource`)
- an invalid slice: `offset` below `0` or not finite, `duration` of `0` or less or not finite (`assignments[i].offset`, `assignments[i].duration`)

In [`Validation.REPAIR`](<https://docs.dhtmlx.com/planning-engine/java/api/project/validation.md>) mode the engine clears an invalid slice and adds a [`Note.Repaired`](<https://docs.dhtmlx.com/planning-engine/java/api/results/note.md#repaired>) instead, so the assignment books the whole task.

## Resources and capacity

Give each resource a `capacity` that matches what it can take. Here one developer and a QA team of two both work on two tasks that run at the same time:

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

public class Capacity {
    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) {
        // Two 2-day tasks, both starting Monday 2026-08-10.
        Moment monday = Moment.fromYmdHm(2026, 8, 10, 8, 0);
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                new TaskData(Id.str("api")).withStartDate(monday).withDuration(2.0),
                new TaskData(Id.str("ui")).withStartDate(monday).withDuration(2.0)))
            .withResources(List.of(
                // One developer: capacity falls back to
                // config.defaultResourceCapacity (1.0).
                new ResourceData().withId(Id.str("dev")),
                // A QA team of two people.
                new ResourceData().withId(Id.str("qa")).withCapacity(2.0)))
            .withAssignments(List.of(
                assign("api", "dev", 1.0),
                assign("ui", "dev", 1.0),
                assign("api", "qa", 1.0),
                assign("ui", "qa", 1.0)));

        ScheduleResult result = Engine.schedule(data, new SolveOptions());
        for (ResourceOverallocation resource : result.overallocations) {
            for (OverallocationWindow window : resource.windows) {
                System.out.println(resource.resource + ": " + window.from + " -> " + window.to
                    + ", peak " + window.peak + " of " + window.capacity);
            }
        }
    }
}
```

Output:

```text
dev: 2026-08-10 08:00 -> 2026-08-11 17:00, peak 2.0 of 1.0
```

Both resources carry a load of `2.0` on Monday and Tuesday. Only `dev` is overallocated, because its capacity is the default `1.0`. `qa` sits exactly at its capacity, and that isn't overallocation.

To change the default for every resource without a `capacity`, set [`config.defaultResourceCapacity`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#defaultresourcecapacity>). With `8.0`, for example, a `value` of `4.0` is half of a resource. A resource with its own `capacity` ignores the default.

In a live [`Project`](<https://docs.dhtmlx.com/planning-engine/java/api/project/project.md>), change capacity with a [`ResourceUpdate`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/resource-update.md>) in [`SchedulingChanges.resources.update`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/scheduling-changes.md#resources>). `withCapacity(Patch.nullValue())` goes back to the default. Removing a resource also removes all its assignments, and [`ApplyReport.effects.removedAssignments`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/apply-report.md#changeeffects>) lists them.

## Assignments

An assignment links a task and a resource by id. One task can have several assignments, and one resource can work on several tasks. The example below books Ana full-time on two parallel tasks and the lead half-time on each, then moves the UI work to Ben:

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

public class Reassign {
    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);
    }

    static void report(String label, ScheduleResult result) {
        List<String> busy = new ArrayList<>();
        for (ResourceOverallocation resource : result.overallocations) {
            busy.add(resource.resource.toString());
        }
        System.out.println(label + ": overallocated " + busy);
    }

    public static void main(String[] args) {
        Moment monday = Moment.fromYmdHm(2026, 8, 10, 8, 0);
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                new TaskData(Id.str("api")).withStartDate(monday).withDuration(2.0),
                new TaskData(Id.str("ui")).withStartDate(monday).withDuration(2.0)))
            .withResources(List.of(
                new ResourceData().withId(Id.str("ana")),
                new ResourceData().withId(Id.str("ben")),
                new ResourceData().withId(Id.str("lead"))))
            .withAssignments(List.of(
                assign("api", "ana", 1.0),
                assign("ui", "ana", 1.0),
                // The lead spends half of their time on each task.
                assign("api", "lead", 0.5),
                assign("ui", "lead", 0.5)));

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

        // Move the UI work from Ana to Ben.
        SchedulingChanges changes = new SchedulingChanges();
        changes.assignments.update.add(
            new AssignmentUpdate(Id.str("ui:ana")).withResource(Patch.of(Id.str("ben"))));
        ApplyReport applied = project.apply(changes);
        if (!applied.rejected.isEmpty()) {
            RejectedOperation rejected = applied.rejected.get(0);
            throw new IllegalStateException(rejected.path + ": " + rejected.message);
        }

        report("after reassign", project.solve(null, new SolveOptions()));
    }
}
```

Output:

```text
initial: overallocated [ana]
after reassign: overallocated []
```

The lead's two half-time assignments add up to `1.0`, so only Ana shows up at first. The [`AssignmentUpdate`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/assignment-update.md>) changes the resource of `ui:ana`. Here the reassignment doesn't change the task dates. Dates change only when the task ends up on a different calendar.

Other assignment edits go through the same [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/scheduling-changes.md>):

- `assignments.add` takes an [`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/assignment-data.md>). With a `null` id the engine generates one and reports it in [`ApplyReport.effects.generatedAssignments`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/apply-report.md#changeeffects>).
- `assignments.update` can change `task`, `resource`, `value`, `offset` and `duration`, each as a [`Patch`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/patch.md>).
- `assignments.remove` takes assignment ids. Removing a task removes its assignments too.

An assignment on an unscheduled task isn't deleted, but it leaves the active plan. It books no load, and the result carries a [`Note.DroppedAssignment`](<https://docs.dhtmlx.com/planning-engine/java/api/results/note.md#droppedassignment>). When the task is scheduled again, the assignment comes back with its slice. A milestone has no span, so its assignments book nothing.

## Assignment slices

By default an assignment books the whole task. Set `offset` and `duration` to book only part of it, for example for a reviewer who joins on the last day. Both are boxed `Double` values of **worked time** in the task's [`durationUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md>), measured on the assignment's calendar:

- `offset`: where the slice starts, counted from the task start. `null` means `0`.
- `duration`: how much work the slice books. `null` means up to the task end.

The example runs the same project twice. The lead reviews the 4-day `build` task, first for all of it, then only on its last working day:

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

public class Slices {
    static ResourceData resource(String id) {
        return new ResourceData().withId(Id.str(id));
    }

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

    static ProjectData project(Double offset, Double duration) {
        Moment monday = Moment.fromYmdHm(2026, 8, 10, 8, 0);
        return new ProjectData()
            .withTasks(List.of(
                // `build`: 4 working days, Monday to Thursday.
                new TaskData(Id.str("build")).withStartDate(monday).withDuration(4.0),
                // `plan`: Monday only.
                new TaskData(Id.str("plan")).withStartDate(monday).withDuration(1.0)))
            .withResources(List.of(resource("dev"), resource("lead")))
            .withAssignments(List.of(
                assign("build", "dev"),
                // The lead reviews `build`: the whole task, or only a slice of it.
                assign("build", "lead").withOffset(offset).withDuration(duration),
                assign("plan", "lead")));
    }

    public static void main(String[] args) {
        SolveOptions options = new SolveOptions().withIncludeUnchanged(true);
        // Whole task, then the last working day only (offset 3 days, 1 day long).
        Double[][] slices = {{null, null}, {3.0, 1.0}};
        for (Double[] slice : slices) {
            ScheduleResult result = Engine.schedule(project(slice[0], slice[1]), options);
            System.out.println("offset " + slice[0] + ", duration " + slice[1]);
            for (Mutation update : result.updates) {
                System.out.println("  " + update.id + ": " + update.startDate + " -> " + update.endDate);
            }
            for (ResourceOverallocation resource : result.overallocations) {
                for (OverallocationWindow window : resource.windows) {
                    System.out.println("  " + resource.resource + " overallocated: "
                        + window.from + " -> " + window.to);
                }
            }
        }
    }
}
```

Output:

```text
offset null, duration null
  build: 2026-08-10 08:00 -> 2026-08-14 08:00
  plan: 2026-08-10 08:00 -> 2026-08-11 08:00
  lead overallocated: 2026-08-10 08:00 -> 2026-08-10 17:00
offset 3.0, duration 1.0
  build: 2026-08-10 08:00 -> 2026-08-14 08:00
  plan: 2026-08-10 08:00 -> 2026-08-11 08:00
```

Without a slice, the lead is booked on `build` and `plan` on Monday. With `offset` `3.0` and `duration` `1.0`, the review books only Thursday, so the conflict goes away. The task dates are identical in both runs: slices change load, never dates.

How slices behave:

- The slice is clipped to the task's work. `withOffset(3.0).withDuration(5.0)` on a 4-day task books only the last day, and an `offset` at or past the task's duration books nothing.
- Clipping happens on every schedule, not on import. If the task gets shorter later, the slice follows it.
- Non-working time and leveling pauses don't count as worked time. A pause or a `levelingDelay` moves the slice together with the work.
- Two assignments of the same resource on the same task book separately, each at its own `value`. Where their slices overlap, the load adds up.
- In [`CalendarMode.RESOURCE`](<https://docs.dhtmlx.com/planning-engine/java/api/data/calendar-mode.md>) a slice is measured on the assignment's calendar. A sliced assignment still counts toward the task's calendar envelope, even when its slice is empty.
- Every load consumer sees the sliced load: `overallocations`, [`loadProfile`](<https://docs.dhtmlx.com/planning-engine/java/api/project-load-profile.md>), the overload metric of [`evaluate`](<https://docs.dhtmlx.com/planning-engine/java/api/project-evaluate.md>) and the decisions of [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>).

In a live project, edit a slice with [`AssignmentUpdate.offset`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/assignment-update.md>) and `AssignmentUpdate.duration`. [`Patch.nullValue()`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/patch.md#nullvalue>) clears a field: `offset` goes back to the task start and `duration` to the task end. Slice edits never change task dates, but they still produce a new baseline id. An invalid value doesn't throw. It comes back as a rejected operation in [`ApplyReport.rejected`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/apply-report.md#rejected>), with a path such as `assignments.update[0]`.

```java
SchedulingChanges changes = new SchedulingChanges();
changes.assignments.update.add(
    new AssignmentUpdate(Id.str("build:lead"))
        .withOffset(Patch.nullValue())
        .withDuration(Patch.nullValue()));
project.apply(changes);
project.solve(null, new SolveOptions());
```

For a step-by-step version, see [Split assignment work](<https://docs.dhtmlx.com/planning-engine/java/how-to/split-assignment-work.md>).

Planning Engine has no assignment mode. Every resource is checked for overallocation, and a `mode` field in JSON input is ignored. Express uneven work with slices.

## Overallocation windows

A window is a run of contiguous overallocated buckets of one resource. Each [`OverallocationWindow`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/resource-overallocation.md#overallocationwindow>) has these fields:

| Field | Meaning |
|---|---|
| `from`, `to` | The window edges as `Moment`s, clipped to the contending tasks' working time (and to their slices) |
| `capacity` | The resource's capacity |
| `peak` | The highest bucket load in the window |
| `overload` | The sum of `load − capacity` over the window's buckets, in capacity × buckets |
| `tasks` | The contending task ids, in input order |
| `reason` | Set only by [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>): why leveling left this window. See [Leveling resources](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md>) |

Non-working time splits windows. Here two 2-day tasks start on a Friday and continue on Monday:

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

public class Windows {
    static AssignmentData assign(String task) {
        // No id: the engine generates one on import.
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str("dev"))
            .withValue(1.0);
    }

    public static void main(String[] args) {
        // Both tasks start on Friday 2026-08-14 and run 2 working days.
        Moment friday = Moment.fromYmdHm(2026, 8, 14, 8, 0);
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                new TaskData(Id.str("api")).withStartDate(friday).withDuration(2.0),
                new TaskData(Id.str("ui")).withStartDate(friday).withDuration(2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev"))))
            .withAssignments(List.of(assign("api"), assign("ui")));

        ScheduleResult result = Engine.schedule(data, new SolveOptions());
        for (ResourceOverallocation resource : result.overallocations) {
            for (OverallocationWindow w : resource.windows) {
                System.out.println(resource.resource + ": " + w.from + " -> " + w.to
                    + " peak=" + w.peak + " capacity=" + w.capacity
                    + " overload=" + w.overload + " tasks=" + w.tasks);
            }
        }
    }
}
```

Output:

```text
dev: 2026-08-14 08:00 -> 2026-08-14 17:00 peak=2.0 capacity=1.0 overload=1.0 tasks=[api, ui]
dev: 2026-08-17 08:00 -> 2026-08-17 17:00 peak=2.0 capacity=1.0 overload=1.0 tasks=[api, ui]
```

The weekend holds no work, so Friday and Monday become two windows. Each one has an `overload` of `1.0`: one day at one unit over capacity. On an hour basis the lunch break splits windows the same way.

What else windows tell you, and what they don't:

- [`overallocations`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#overallocations>) is `null` when the project has no assignments, and an empty `Vec` when there's no contention. Check for `null` before you loop if a project can come without assignments. A resource appears only when it has at least one window, and resources keep their input order.
- Windows follow the scheduled dates, not the stored ones. If a link pushes `ui` after `api`, the contention is gone and so is the window.
- Detection works in whole buckets, but `from` and `to` are clipped to working time. Two 2-day tasks that start Monday end Wednesday 08:00, yet their window ends Tuesday 17:00, because Wednesday before 08:00 holds no working time.
- Per resource, the windows' `overload` values add up to the overload that [`evaluate`](<https://docs.dhtmlx.com/planning-engine/java/api/project-evaluate.md>) reports for that resource at the same basis.

For a per-bucket view of load and capacity (a resource histogram), use [`loadProfile`](<https://docs.dhtmlx.com/planning-engine/java/api/project-load-profile.md>). See [Resource load](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/resource-load.md>).
