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


# Leveling Resources

This guide shows how to remove resource overallocation from a plan. It covers what [`Project#level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) changes, how it picks the task that gives way, how to limit it to part of the plan, and how to read, preview and undo its result.

## How leveling works

A resource is overallocated when the tasks booked on it need more than its capacity at the same time. [`solve`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>) only reports this, as [`overallocations`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#overallocations>) windows on the result. [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) fixes it by moving work later. It never changes assignments, resources or capacities. It changes two task fields:

- `levelingDelay`, which delays the whole task.
- `pauses`, which splits a task: it stops working for a while and picks up again later.

Leveling looks at the plan in **buckets**: day buckets when [`config.durationUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#durationunit>) is `DAY`, hour buckets for `HOUR` and `MINUTE`. The **load** of a resource in a bucket is the sum of `value` over the assignments that book working time in that bucket. A task counts with its full `value` as soon as it works any time in the bucket. When the load is higher than the resource's `capacity` (default [`config.defaultResourceCapacity`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#defaultresourcecapacity>), which is 1.0), the bucket is overallocated. A run of overallocated buckets in a row is a **window**.

`level` works through the plan from left to right, in rounds:

1. Find the earliest overallocated bucket.
2. Collect the tasks that contribute to it, the **contenders**, and sort them by the selection order.
3. Move the weakest contender just past the bucket, with a delay or a pause, until the load fits.
4. Reschedule the project, so links and constraints push the successors along.
5. Repeat until no overallocated bucket with a movable contender is left.

Some tasks never move. Leveling leaves them alone and calls them the **fixed set**:

- tasks with `autoScheduling = false`
- completed tasks (`progress == 1`, when `config.useProgress` is on)
- tasks with an `MSO` or `MFO` constraint (when `config.applyConstraints` is on)
- tasks with `priority == 1000`
- summaries (their dates follow their children)
- tasks outside [`LevelingScope.tasks`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#levelingscope>), when you set it

If a proposed move doesn't stick, because a constraint or another hard rule pulls the task back, leveling reverts it and freezes the task for the rest of the run. Hard requirements always win. Whatever leveling can't resolve stays in `overallocations`, and each remaining window gets a `reason`.

A few rules hold for every run:

- `level` commits. It runs `solve(null, …)` first, writes the delays and pauses into the project and accepts the leveled dates as the new baseline. To show leveling as a proposal, run it on a [`copy()`](<https://docs.dhtmlx.com/planning-engine/java/api/project/project.md#copy>). See [Previewing and clearing](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md#previewing-and-clearing>).
- Leveling is forward-only. A project with `config.scheduleFromEnd = true` is refused with an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) at path `config.schedule_from_end`.
- A cyclic project isn't leveled. `level` returns the initial solve's result, with its cycles, and `rounds = 0`.
- The output is deterministic. Every tie ends in input order, so the same data and options always give the same result.
- There's no round limit. A run ends when nothing is left to resolve. The call is synchronous and CPU-bound, so on large projects run it on a worker executor in a server.
- Known bug: `level` doesn't return when an assignment requests more units than its resource can ever supply. Check `value` against `capacity` before you call it.

## Leveling a project

Call [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) with a `new` [`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md>) after the project is solved. The examples on this page use the default calendar (Monday to Friday, 08:00-12:00 and 13:00-17:00) and start on Monday, August 10, 2026.

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

public class LevelProject {
    static Moment aug(int day) {
        return Moment.fromYmdHm(2026, 8, day, 8, 0);
    }

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

    static AssignmentData booking(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 {
        // Two 2-day tasks, both booked full time on dev1 from Monday.
        ProjectData data = new ProjectData()
            .withTasks(List.of(task("A", aug(10), 2.0), task("B", aug(10), 2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(booking("A", "dev1"), booking("B", "dev1")));

        Project project = new Project();
        ScheduleResult solved = project.solve(data, new SolveOptions());
        for (ResourceOverallocation over : solved.overallocations) {
            for (OverallocationWindow window : over.windows) {
                // dev1: 2026-08-10 08:00 .. 2026-08-11 17:00, peak 2.0 of 1.0
                System.out.println(over.resource + ": " + window.from + " .. " + window.to
                    + ", peak " + window.peak + " of " + window.capacity);
            }
        }

        LevelingResult leveled = project.level(new LevelingOptions());
        for (Mutation update : leveled.schedule.updates) {
            // B: 2026-08-12 08:00 .. 2026-08-14 08:00, delay 2.0
            System.out.println(update.id + ": " + update.startDate + " .. " + update.endDate
                + ", delay " + update.levelingDelay);
        }
    }
}
```

[`solve`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>) reports one window on `dev1` over Monday and Tuesday, with a peak load of 2.0. `level` keeps `A` and moves `B` to Wednesday. Both tasks are equal, so the input-order tie-break decides: the later task moves.

`overallocations` is a list only when the project has assignments. Without any, it's `null`.

Leveling diffs its `updates` against the state the call started from, so you can apply them to the model you already hold. Next to the new dates, each [`Mutation`](<https://docs.dhtmlx.com/planning-engine/java/api/results/mutation.md>) carries the task's new `levelingDelay` and, when it changed, its complete `pauses` list. Fields that didn't change are `null`. A `Mutation` maps field-for-field onto a [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/task-update.md>).

## Delays and pauses

`levelingDelay` and `pauses` are **elapsed** time, in the task's duration unit: with day units, 1.0 is 24 hours of calendar time, weekends and nights included. `duration` and `lag` stay working time. Both round to whole minutes when they're used.

- A delay shifts the task's start from its driven position (the start that links and constraints give it). Draw it as the stretch `[start - delay, start)` before the bar.
- A [`Pause`](<https://docs.dhtmlx.com/planning-engine/java/api/data/pause.md>) starts `at` after the task start and lasts `length`. The leveler sets `by` to [`PauseSource.LEVELING`](<https://docs.dhtmlx.com/planning-engine/java/api/data/pause.md#pausesource>) on every pause it writes ([`Pause.leveling(at, length)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/pause.md#leveling>) builds the same value). Draw pauses as gaps in the bar. They need no calendar.

Which one leveling picks depends on the contender. A task that starts inside the overallocated bucket has no work before it, so it gets a whole-task delay. A task that's already running in that bucket can be paused: it keeps the work it has done and resumes after the contention. Delaying a running task would leave an idle gap behind it. Pausing it doesn't. Set [`allowSplits`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#allowsplits>) to `false` to get whole-task delays only.

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

public class DelaysAndPauses {
    static Moment aug(int day) {
        return Moment.fromYmdHm(2026, 8, day, 8, 0);
    }

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

    static AssignmentData booking(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 {
        // A runs Monday and Tuesday. B joins on Tuesday and feeds C, so it has less slack.
        ProjectData data = new ProjectData()
            .withTasks(List.of(
                task("A", aug(10), 2.0),
                task("B", aug(11), 2.0),
                task("C", aug(13), 5.0)))
            .withLinks(List.of(new LinkData()
                .withId(Id.str("bc"))
                .withSource(Id.str("B"))
                .withTarget(Id.str("C"))))
            .withResources(List.of(
                new ResourceData().withId(Id.str("dev1")),
                new ResourceData().withId(Id.str("dev2"))))
            .withAssignments(List.of(
                booking("A", "dev1"),
                booking("B", "dev1"),
                booking("C", "dev2")));

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

        // Splits allowed (the default): A is paused, B and C keep their dates.
        LevelingResult split = project.copy().level(new LevelingOptions());
        for (Mutation update : split.schedule.updates) {
            // A: start null, end 2026-08-14 08:00, pauses [Pause{at=1.0, length=2.0, by=LEVELING}]
            System.out.println(update.id + ": start " + update.startDate
                + ", end " + update.endDate + ", pauses " + update.pauses);
        }

        // Whole-task delays only: B moves past A, and C moves with it.
        LevelingResult whole = project.copy().level(new LevelingOptions().withAllowSplits(false));
        for (Mutation update : whole.schedule.updates) {
            // B: 2026-08-12 08:00 .. 2026-08-14 08:00
            // C: 2026-08-14 08:00 .. 2026-08-21 08:00
            System.out.println(update.id + ": " + update.startDate + " .. " + update.endDate);
        }
    }
}
```

With splits, `A` keeps its start and works Monday, pauses for two days (`at = 1.0`, `length = 2.0`), and finishes its second day on Thursday. `B` works in the pause, so the chain into `C` doesn't move. Without splits, `A` can't be paused and stays in place, so `B` is delayed whole and the link pushes `C` along. The update for `A` has `startDate == null`, because its start didn't change.

Every pause is treated as leveling's own: `clearFirst` clears all of them, and the leveler may extend any. A pause takes up no load, and a task without pauses never gets a `pauses` list in its update. The validation rules are on the [`Pause` reference](<https://docs.dhtmlx.com/planning-engine/java/api/data/pause.md>).

## Leveling options

[`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md>) starts with every default set. Change fields with the fluent setters (`withAllowSplits(false)`) or assign them directly (`options.allowSplits = false`):

| Field | Default | What it changes |
|---|---|---|
| `scope` | `null` = whole project | Which resources, tasks and dates leveling acts on. |
| `basis` | `null` = derived from `config.durationUnit` | Bucket size, [`Basis.DAY`](<https://docs.dhtmlx.com/planning-engine/java/api/data/basis.md>) or `Basis.HOUR`. |
| `order` | [`LevelingOrder.STANDARD`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#levelingorder>) | Which contender moves first. See [Selection order](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md#selection-order>). |
| `withinSlackOnly` | `false` | Never push a task past the project end of the plan leveling started from. |
| `clearFirst` | `true` | Clear the existing delays and pauses on in-scope tasks before leveling. |
| `allowSplits` | `true` | Allow `pauses`. With `false`, every new move is a whole-task delay. |
| `step` | [`LevelingStep.MINIMAL`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#levelingstep>) | How far one move goes. |

The full list is on the [`LevelingOptions` reference](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md>).

### Leveling part of a plan

[`LevelingScope`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#levelingscope>) limits a run in three ways, and you can combine them. Each field is `null` by default, which means no limit:

- `tasks`: only these tasks may move. Everything else acts like a fixed task.
- `resources`: only contention on these resources triggers a move.
- `from` / `to`: only buckets that start in `[from, to)` trigger a move. The range limits where leveling looks for contention, not where tasks land: a moved task may end up past `to`.

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

public class ScopedLeveling {
    static Moment aug(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 booking(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", aug(10, 8), 2.0), task("B", aug(10, 8), 2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(booking("A", "dev1"), booking("B", "dev1")));
        Project project = new Project();
        project.solve(data, new SolveOptions());

        // Only A may move, so A gives way even though B is later in input order.
        LevelingOptions onlyA = new LevelingOptions()
            .withScope(new LevelingScope().withTasks(List.of(Id.str("A"))));
        LevelingResult result = project.copy().level(onlyA);
        for (Mutation update : result.schedule.updates) {
            // A: 2026-08-12 08:00 .. 2026-08-14 08:00
            System.out.println(update.id + ": " + update.startDate + " .. " + update.endDate);
        }

        // Only contention from Wednesday on. The Monday-Tuesday window stays.
        LevelingOptions fromWednesday = new LevelingOptions()
            .withScope(new LevelingScope().withFrom(aug(12, 0)));
        result = project.copy().level(fromWednesday);
        for (ResourceOverallocation over : result.schedule.overallocations) {
            for (OverallocationWindow window : over.windows) {
                // dev1: 2026-08-10 08:00 .. 2026-08-11 17:00, out-of-scope
                System.out.println(over.resource + ": " + window.from + " .. " + window.to
                    + ", " + window.reason);
            }
        }
    }
}
```

The second run leaves the Monday window alone and reports it with the reason `out-of-scope`. The result still has `converged == true`, because the run did everything it was asked to do. The reason tells you why the window is still there.

An unknown id in `scope.tasks` or `scope.resources` throws an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) at path `scope.tasks` or `scope.resources`.

### Staying within slack

With [`withinSlackOnly = true`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#withinslackonly>), leveling never pushes a task further than its total slack. The slack is measured against the project end of the plan the run started from, so leveling can't make the project finish later. If a contender can't clear the bucket within its slack, leveling skips it and tries the next one. If no contender fits, the window stays with the reason `slack-exhausted`.

```java
LevelingResult result = project.level(new LevelingOptions().withWithinSlackOnly(true));
```

The slack leveling uses is internal. It ignores the existing delays and uses that fixed project end, so it can differ from [`analytics.slack`](<https://docs.dhtmlx.com/planning-engine/java/api/results/analytics.md#slack>) in a [`solve`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>) result.

### Fewer rounds with the skip step

By default a move clears one bucket. Then leveling reschedules and re-selects at the next bucket, so the burden of a long window can switch between contenders bucket by bucket. [`LevelingStep.SKIP`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#levelingstep>) moves the task past the whole window in one move. That takes fewer rounds and reschedules less often. Use it for large projects where [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) is slow.

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

public class SkipStep {
    static Moment aug(int day) {
        return Moment.fromYmdHm(2026, 8, day, 8, 0);
    }

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

    static AssignmentData booking(String task) {
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str("dev1"))
            .withValue(1.0);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(task("A", aug(10), 2.0), task("B", aug(10), 2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(booking("A"), booking("B")));
        Project project = new Project();
        project.solve(data, new SolveOptions());

        LevelingResult minimal = project.copy().level(new LevelingOptions());
        LevelingResult skip = project.copy().level(
            new LevelingOptions().withStep(LevelingStep.SKIP));

        // Same dates for B (Wednesday to Friday), in 2 rounds and in 1.
        System.out.println("minimal: " + minimal.rounds + " rounds, skip: " + skip.rounds + " rounds");
        System.out.println(minimal.schedule.updates.equals(skip.schedule.updates)); // true
    }
}
```

Here both steps give the same plan. That's not guaranteed: buckets the skip step jumps over never get a new selection, so on real plans the two can level differently. If a jump hits a constraint or doesn't fit the slack, leveling falls back to the one-bucket move for that task.

## Selection order

When several contenders share an overallocated bucket, the order decides which one stays and which moves first. Every order ends in the same tie-break: the task later in input order moves.

| [`LevelingOrder`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#levelingorder>) | Who moves first |
|---|---|
| `STANDARD` (default) | Checked in order: a successor before its predecessor, more total slack, later start, lower `priority`, no dated constraint. |
| `PRIORITY` | Lower `priority` first, then the `STANDARD` checks. |
| `ID` | The task later in input order. |

All three orders also apply the running rule. A task already working before the bucket stays, and the task that starts in the bucket moves, unless the running task can be paused instead. This keeps whole-task delays from leaving gaps in work that was already scheduled. Under `PRIORITY`, a higher priority beats the running rule: the lower-priority running task is paused, or delayed whole when splits are off.

`priority` is an `int` from 0 to 1000 on [`TaskData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md>), 500 by default. At 1000 the task never moves.

The dependency check exists because delaying a predecessor would push its successor along and resolve nothing. In this plan `B` comes first in the input, but an SS link from `A` makes `A` its predecessor, so `B` moves:

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

public class SelectionOrder {
    static Moment aug(int day) {
        return Moment.fromYmdHm(2026, 8, day, 8, 0);
    }

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

    static AssignmentData booking(String task) {
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str("dev1"))
            .withValue(1.0);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(task("B", aug(10), 2.0), task("A", aug(10), 2.0)))
            .withLinks(List.of(new LinkData()
                .withId(Id.str("ab"))
                .withSource(Id.str("A"))
                .withTarget(Id.str("B"))
                .withLinkType(LinkType.SS)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(booking("A"), booking("B")));
        Project project = new Project();
        project.solve(data, new SolveOptions());

        LevelingResult result = project.level(new LevelingOptions());
        for (Mutation update : result.schedule.updates) {
            // B: 2026-08-12 08:00 .. 2026-08-14 08:00
            System.out.println(update.id + ": " + update.startDate + " .. " + update.endDate);
        }
    }
}
```

To protect a task from leveling, set its `priority` to 1000 or its `autoScheduling` to `false`. With `withPriority(1000)` on `B` in the first example, `A` moves to Wednesday instead.

## Reading the result

[`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) returns a [`LevelingResult`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-result.md>):

- `schedule`: a [`ScheduleResult`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md>), the same shape `solve` returns. Its `updates` carry the new dates, delays and pauses. `baselineId` names the new baseline. The project keeps this object as its baseline result, so treat it as read-only, or [`deepClone()`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#deepclone>) it before you change it.
- `rounds`: the number of rounds that proposed a move. Rounds that only skipped an unresolvable bucket don't count.

[`schedule.converged`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#converged>) is the scheduler's convergence on the final plan, not a leveling flag. Leveling itself always runs until nothing is left to resolve.

`schedule.overallocations` lists the windows that remain. Every window from `level` has a `reason`. When a window has several immovable contenders, the first reason in this table wins:

| `reason` | Why the window remains |
|---|---|
| `pinned-constraint` | A contender was frozen because its move didn't stick, or it has an `MSO` / `MFO` constraint. |
| `fixed-task` | A contender has `autoScheduling = false`, or is a summary. |
| `completed` | A contender has `progress == 1` under `config.useProgress`. |
| `priority-1000` | A contender has `priority == 1000`. |
| `slack-exhausted` | Every candidate was capped by `withinSlackOnly`. |
| `out-of-scope` | The resource, the dates or every contender is outside `scope`. |

A reason is data, not an error. It tells the user what to change to resolve the window: unpin a task, loosen a constraint, widen the scope.

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

public class Reasons {
    static Moment aug(int day) {
        return Moment.fromYmdHm(2026, 8, day, 8, 0);
    }

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

    static AssignmentData booking(String task) {
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str("dev1"))
            .withValue(1.0);
    }

    public static void main(String[] args) throws Exception {
        // A is fixed. B is the only task that could move, but it must start by Monday.
        TaskData a = task("A", aug(10), 2.0).withAutoScheduling(false);
        TaskData b = task("B", aug(10), 2.0)
            .withConstraintType(ConstraintType.SNLT)
            .withConstraintDate(aug(10));

        ProjectData data = new ProjectData()
            .withTasks(List.of(a, b))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(booking("A"), booking("B")));
        data.config.applyConstraints = true;

        Project project = new Project();
        project.solve(data, new SolveOptions());
        LevelingResult result = project.level(new LevelingOptions());

        // The move was reverted, so nothing changed.
        System.out.println(result.schedule.updates.isEmpty()); // true
        for (ResourceOverallocation over : result.schedule.overallocations) {
            for (OverallocationWindow window : over.windows) {
                // dev1: tasks [A, B], reason pinned-constraint
                System.out.println(over.resource + ": tasks " + window.tasks
                    + ", reason " + window.reason);
            }
        }
    }
}
```

Leveling proposed a delay for `B`, the replan pulled `B` back to Monday, and leveling reverted the move. Nothing in the project changed. The window stays and reports `pinned-constraint`. With both tasks fixed and no constraint, the same window would report `fixed-task`.

The other window fields (`from`, `to`, `capacity`, `peak`, `overload`, `tasks`) are on the [`OverallocationWindow` reference](<https://docs.dhtmlx.com/planning-engine/java/api/resources/resource-overallocation.md#overallocationwindow>). For the load of each bucket, see [Resource load](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/resource-load.md>).

`level` throws an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) for an unknown scope id or a backward-scheduled project, and a [`ProjectException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/project-exception.md>) with code `NEEDS_RESYNC` after a rejected [`apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>). Both leave the project as it was. See [Error handling](<https://docs.dhtmlx.com/planning-engine/java/error-handling.md>).

## Previewing and clearing

Because [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) commits, run it on a copy to show leveling as a proposal. [`Project#copy`](<https://docs.dhtmlx.com/planning-engine/java/api/project/project.md#copy>) is cheap: the data is shared until one copy is edited.

```java
static Project levelingProposal(Project project) {
    Project draft = project.copy();
    LevelingResult proposal = draft.level(new LevelingOptions());
    for (Mutation update : proposal.schedule.updates) {
        System.out.println(update.id + ": delay " + update.levelingDelay
            + ", pauses " + update.pauses);
    }
    // Accept: keep `draft` in place of `project`. Reject: drop it.
    return draft;
}
```

The step-by-step version is in [Preview leveling](<https://docs.dhtmlx.com/planning-engine/java/how-to/preview-leveling.md>). To score leveling as part of an alternative change set, use [`evaluate`](<https://docs.dhtmlx.com/planning-engine/java/api/project-evaluate.md>) with [`EvaluateOptions.level`](<https://docs.dhtmlx.com/planning-engine/java/api/evaluate/evaluate-options.md#level>). See [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/evaluating-alternatives.md>).

There are two ways to remove leveling:

- To undo it, set `levelingDelay` to 0.0 and `pauses` to an empty list on the task, then [`solve`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>). The original contention comes back.
- To level again from scratch, call `level` again. [`clearFirst`](<https://docs.dhtmlx.com/planning-engine/java/api/resources/leveling-options.md#clearfirst>) is on by default, so the run first clears the delays and pauses of the in-scope tasks and levels from the clean plan. With `clearFirst = false` the existing delays and pauses stay, and leveling adds to them. Use that to keep a deliberate gap the user set by hand.

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

public class ClearLeveling {
    static Moment aug(int day) {
        return Moment.fromYmdHm(2026, 8, day, 8, 0);
    }

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

    static AssignmentData booking(String task) {
        return new AssignmentData()
            .withTask(Id.str(task))
            .withResource(Id.str("dev1"))
            .withValue(1.0);
    }

    public static void main(String[] args) throws Exception {
        ProjectData data = new ProjectData()
            .withTasks(List.of(task("A", aug(10), 2.0), task("B", aug(10), 2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(booking("A"), booking("B")));
        Project project = new Project();
        project.solve(data, new SolveOptions());
        project.level(new LevelingOptions()); // B moves to Wednesday

        // Undo leveling on B.
        SchedulingChanges changes = new SchedulingChanges();
        changes.tasks.update.add(new TaskUpdate(Id.str("B"))
            .withLevelingDelay(Patch.of(0.0))
            .withPauses(Patch.of(new Vec<Pause>())));
        project.apply(changes);

        ScheduleResult replanned = project.solve(null, new SolveOptions());
        for (Mutation update : replanned.updates) {
            // B: 2026-08-10 08:00 .. 2026-08-12 08:00
            System.out.println(update.id + ": " + update.startDate + " .. " + update.endDate);
        }
        // dev1 is overallocated on Monday and Tuesday again.
        System.out.println(replanned.overallocations.get(0).windows.size()); // 1
    }
}
```

The `levelingDelay` update moves `B`'s stored start back by the same amount, so `B` returns to Monday and the window over Monday and Tuesday comes back. To clear every task, put one such update per task with a delay or pauses into the same change set. The full recipe is in [Clear leveling](<https://docs.dhtmlx.com/planning-engine/java/how-to/clear-leveling.md>).
