[Canonical page](<https://docs.dhtmlx.com/planning-engine/java/scheduling/project-data/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Project data

This guide shows how to describe a plan for Planning Engine: tasks, links, ids, dates, durations, and the config switches that change how the plan is scheduled. It also lists what strict validation rejects, so you can build valid input the first time.

## How project data works

A plan goes in as one [`ProjectData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/project-data.md>) object. It holds six fields: `config`, `calendars`, `tasks`, `links`, `resources` and `assignments`. Only `tasks` is required. The lists start empty, and `config` starts as `new SchedulingConfig()`.

`ProjectData` is an import format, not stored state. [`Project#solve`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>), [`Engine.schedule`](<https://docs.dhtmlx.com/planning-engine/java/api/engine-schedule.md>) and [`Project#analyze`](<https://docs.dhtmlx.com/planning-engine/java/api/project-analyze.md>) validate it and build the engine's own normalized model from it. `solve` deep-copies the object first, so changing it afterwards doesn't touch the project. The engine never hands the snapshot back. Results report changes as sparse `updates`, and you write them back into your own model.

The records are mutable classes with public fields and a fluent `withX(value)` setter per field:

- [`new TaskData(id)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md#taskdataid>) gives a task with every default filled in. Chain the setters you need on top of it.
- [`LinkData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md>), [`CalendarData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/calendar-data.md>), [`ResourceData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/resource-data.md>) and [`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/assignment-data.md>) have a no-argument constructor. Set their fields with `withX`.
- `ProjectData` and `SchedulingConfig` start with every default, so you set only what differs.
- List fields are [`Vec<T>`](<https://docs.dhtmlx.com/planning-engine/java/api/support/vec.md>), an `ArrayList<T>`. Every list setter also takes a `java.util.Collection`, so `withTasks(List.of(...))` works.

Field names are camelCase (`startDate`, `duration`, `constraintType`, `autoScheduling`). In JSON they are snake_case (`start_date`, `constraint_type`), and `taskType` / `linkType` are called `type`. To load JSON, call [`Decode.fromJson(json, ProjectData.class)`](<https://docs.dhtmlx.com/planning-engine/java/api/json/decode.md#fromjson>). Unknown JSON fields in the data are ignored.

A few rules hold everywhere:

- [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) is a number or a string and compares like a JavaScript object key, so task `1` and task `"1"` are the same task.
- [`Moment`](<https://docs.dhtmlx.com/planning-engine/java/api/data/moment.md>) is a naive local date-time at minute precision. There's no time zone, no DST and no "today".
- `duration` and `lag` are working time, measured on calendars. `levelingDelay` and `pauses` are elapsed wall-clock time.
- An optional field is `null` when absent (`Double duration`, `Moment startDate`, `Id parent`).
- Strict validation (the default) rejects ambiguous input with a path-qualified [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) instead of guessing. [`Validation.REPAIR`](<https://docs.dhtmlx.com/planning-engine/java/api/project/validation.md>) fixes what it can and reports each fix.

For every field and its default, see [`ProjectData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/project-data.md>), [`TaskData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md>) and [`SchedulingConfig`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md>).

## Describing a project

### Tasks

A leaf task needs two of `startDate`, `endDate` and `duration`. The engine derives the third. If you send all three, they must agree on the task's calendar.

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

public class TasksExample {
    public static void main(String[] args) throws Exception {
        Moment monday = Moment.fromYmdHm(2026, 3, 2, 8, 0);

        // A summary with no dates: its span comes from its children.
        TaskData phase = new TaskData(Id.str("phase")).withTaskType(TaskType.SUMMARY);

        TaskData design = new TaskData(Id.str("design"))
                .withParent(Id.str("phase"))
                .withStartDate(monday)
                .withDuration(3.0);

        TaskData build = new TaskData(Id.str("build"))
                .withParent(Id.str("phase"))
                .withStartDate(monday)
                .withDuration(5.0);

        // A milestone needs only one date.
        TaskData release = new TaskData(Id.str("release"))
                .withTaskType(TaskType.MILESTONE)
                .withStartDate(monday);

        ProjectData data = new ProjectData()
                .withTasks(List.of(phase, design, build, release))
                .withLinks(List.of(
                        new LinkData()
                                .withId(Id.str("l1"))
                                .withSource(Id.str("design"))
                                .withTarget(Id.str("build")),
                        new LinkData()
                                .withId(Id.str("l2"))
                                .withSource(Id.str("build"))
                                .withTarget(Id.str("release"))));

        ScheduleResult result = Engine.schedule(data, new SolveOptions());
        for (Mutation update : result.updates) {
            if (update.startDate != null && update.endDate != null) {
                System.out.println(update.id + ": " + update.startDate + " .. " + update.endDate);
            }
        }
    }
}
```

The links push `build` after `design` and `release` after `build`, and only those two tasks appear in `updates`. Both links use the default [`LinkType.FS`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md#linktype>) with no lag. The summary `phase` has no dates of its own, so its span comes from `design` and `build`.

Each task type has its own date rule:

- [`TaskType.TASK`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md#tasktype>) (the default) is a leaf: two of start, end and duration.
- `TaskType.SUMMARY` takes both dates or neither. Its span is derived, so a summary with `startDate` + `duration` is rejected. A summary with no dates needs at least one dated child.
- `TaskType.MILESTONE` needs `startDate` or `endDate`. Its duration is 0.

`parent` links a task to its summary. `null`, `Id.str("")` and a value equal to `config.rootId` (default `Id.num(0)`) all mean "no parent". A parent that doesn't exist, or a `parent` chain that loops back on itself, is a validation error.

The rest of [`TaskData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md>) covers constraints (`constraintType`, `constraintDate`), calendars (`calendarId`), and leveling inputs (`priority`, `levelingDelay`, `pauses`). See [Links, constraints and summaries](<https://docs.dhtmlx.com/planning-engine/java/scheduling/links-constraints-summaries.md>), [Working time and calendars](<https://docs.dhtmlx.com/planning-engine/java/scheduling/working-time-calendars.md>) and [Leveling resources](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md>).

### Ids and dates

Build an [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) with [`Id.num(double)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md#num>) or [`Id.str(String)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md#str>). Ids compare by their JavaScript key, [`Id#key()`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md#key>), so a number and its string form are the same id. `equals`, `hashCode` and `toString` all follow the key.

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

public class IdsExample {
    public static void main(String[] args) {
        Id numeric = Id.num(1);
        Id text = Id.str("1");
        System.out.println(numeric.equals(text)); // true

        // -0 and 0 share the key "0".
        System.out.println(Id.num(-0.0).equals(Id.str("0"))); // true

        System.out.println(numeric); // prints the key: 1
    }
}
```

This matters when you mix sources. Task `1` from one system and task `"1"` from another collide as duplicates. Numeric ids are `double`, so integers past 2^53 lose precision just as they do in JavaScript.

[`Moment`](<https://docs.dhtmlx.com/planning-engine/java/api/data/moment.md>) is a local date-time with minute precision. There are three ways to build one:

- [`Moment.fromYmdHm(...)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/moment.md#fromymdhm>) returns `null` for an impossible date.
- [`Moment.fromDateTime(LocalDateTime)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/moment.md#fromdatetime>) drops seconds.
- [`Moment.parse(String)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/moment.md#parse>) reads `YYYY-M-D` with an optional time. It throws `IllegalArgumentException` for bad input.

```java
import java.time.LocalDateTime;
import org.dhtmlx.plen.*;

public class MomentsExample {
    public static void main(String[] args) {
        Moment a = Moment.fromYmdHm(2026, 3, 2, 9, 30);
        Moment b = Moment.fromDateTime(LocalDateTime.of(2026, 3, 2, 9, 30, 45)); // 09:30:45 becomes 09:30
        Moment c = Moment.parse("2026-03-02 09:30");

        System.out.println(a.equals(b) && b.equals(c)); // true
        System.out.println(a); // 2026-03-02 09:30
    }
}
```

`Moment` carries no time zone. Convert zoned timestamps to the plan's local wall-clock time before you build the data. The engine never reads the clock, so an entirely undated project is rejected instead of being placed at "today". [Records, ids and dates](<https://docs.dhtmlx.com/planning-engine/java/records-ids-and-dates.md>) covers both types in more depth.

### Durations and units

`duration` is working time in a unit: [`TimeUnit.MINUTE`](<https://docs.dhtmlx.com/planning-engine/java/api/data/time-unit.md>), `TimeUnit.HOUR` or `TimeUnit.DAY`. `config.durationUnit` sets the unit for the whole project (default `DAY`). A task's own `durationUnit` overrides it for that task.

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

public class DurationsExample {
    public static void main(String[] args) throws Exception {
        Moment monday = Moment.fromYmdHm(2026, 3, 2, 8, 0);

        // 12 working hours: the default calendar works 08-12 and 13-17.
        TaskData review = new TaskData(Id.str("review"))
                .withStartDate(monday)
                .withDuration(12.0);

        // This task counts in minutes instead of the project's hours.
        TaskData sync = new TaskData(Id.str("sync"))
                .withStartDate(monday)
                .withDuration(90.0)
                .withDurationUnit(TimeUnit.MINUTE);

        ProjectData data = new ProjectData()
                .withConfig(new SchedulingConfig().withDurationUnit(TimeUnit.HOUR))
                .withTasks(List.of(review, sync));

        ScheduleResult result = Engine.schedule(data, new SolveOptions());
        System.out.println(result.updates.size() + " updates");
    }
}
```

Durations are `Double`, so `1.5` days or `0.25` hours are valid. A negative or non-finite duration is rejected. `TimeUnit` has no week, month or year, and `Decode` rejects those spellings in JSON with `unknown duration unit`.

Day units count whole working days. From a midnight anchor, Tuesday 00:00 + 4 days ends at Saturday 00:00, which is the close of Friday. From a non-midnight anchor, days are counted to the same time of day. With a sparse calendar, that can skip working hours: on a Mon-Wed 9:00-13:00 calendar, a 3-day task from Monday 9:00 ends the next Monday 9:00. If that matters, use hour units.

Two task fields use elapsed time instead of working time: `levelingDelay` and `pauses`. Both are in the task's `durationUnit`, where a day is 24 hours, and both are rounded to whole minutes on use. The leveler writes them. Read [Leveling resources](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/leveling-resources.md>) before you set them by hand.

### Links

A [`LinkData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md>) connects a `source` task to a `target` task. `linkType` is one of [`LinkType.FS`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md#linktype>) (finish-to-start, the default), `SS`, `FF` or `SF`. `lag` is working time added along the link, in `config.durationUnit` unless the link sets its own `lagUnit`.

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

public class LinksExample {
    public static void main(String[] args) throws Exception {
        Moment monday = Moment.fromYmdHm(2026, 3, 2, 8, 0);
        List<TaskData> tasks = List.of(
                new TaskData(Id.str("a")).withStartDate(monday).withDuration(2.0),
                new TaskData(Id.str("b")).withStartDate(monday).withDuration(2.0),
                new TaskData(Id.str("c")).withStartDate(monday).withDuration(2.0));

        List<LinkData> links = List.of(
                // b starts 1 working day after a starts.
                new LinkData()
                        .withId(Id.str("ss"))
                        .withSource(Id.str("a"))
                        .withTarget(Id.str("b"))
                        .withLinkType(LinkType.SS)
                        .withLag(1.0),
                // c finishes 4 working hours after b finishes.
                new LinkData()
                        .withId(Id.str("ff"))
                        .withSource(Id.str("b"))
                        .withTarget(Id.str("c"))
                        .withLinkType(LinkType.FF)
                        .withLag(4.0)
                        .withLagUnit(TimeUnit.HOUR),
                // Points at a task that doesn't exist: dropped, not an error.
                new LinkData()
                        .withId(Id.str("stray"))
                        .withSource(Id.str("c"))
                        .withTarget(Id.str("missing")));

        ProjectData data = new ProjectData().withTasks(tasks).withLinks(links);

        ScheduleResult result = Engine.schedule(data, new SolveOptions());
        for (Note note : result.notes) {
            if (note instanceof Note.DroppedLink dropped) {
                System.out.println("link " + dropped.link + " dropped: " + dropped.reason);
            }
        }
    }
}
```

Leave `lagUnit` as `null` to measure lag in the project's `durationUnit`.

Some links can't apply. The engine drops them and reports a [`Note.DroppedLink`](<https://docs.dhtmlx.com/planning-engine/java/api/results/note.md#droppedlink>) with the reason instead of failing:

- the source or target task doesn't exist;
- the source or target task is unscheduled;
- the link joins a summary to one of its own descendants, and `config.descendantLinks` is `false`.

A duplicate link id or a non-finite `lag` is a validation error. A link from a task to itself stays in the plan and shows up as a one-task cycle. How each link type moves its target, and how links attach to summaries, is in [Links, constraints and summaries](<https://docs.dhtmlx.com/planning-engine/java/scheduling/links-constraints-summaries.md>).

### Fixed, unscheduled and completed tasks

Three task states keep a task where it is:

- `autoScheduling = false` makes the task fixed. The planner never moves it, but links from it still push its successors.
- `unscheduled = true` takes the task out of the plan. It isn't scheduled or reported, and it doesn't need dates. Its descendants go with it. Links that touch it are dropped with a note.
- With `config.useProgress` on, a task with `progress == 1.0` is completed and stays put.

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

public class FixedTasksExample {
    public static void main(String[] args) throws Exception {
        Moment monday = Moment.fromYmdHm(2026, 3, 2, 8, 0);

        TaskData done = new TaskData(Id.str("done"))
                .withStartDate(monday)
                .withDuration(2.0)
                .withProgress(1.0); // completed, because useProgress is on

        TaskData pinned = new TaskData(Id.str("pinned"))
                .withStartDate(monday)
                .withDuration(1.0)
                .withAutoScheduling(false); // fixed

        TaskData later = new TaskData(Id.str("later"))
                .withUnscheduled(true); // no dates needed

        ProjectData data = new ProjectData()
                .withConfig(new SchedulingConfig().withUseProgress(true))
                .withTasks(List.of(done, pinned, later));

        ScheduleResult result = Engine.schedule(data, new SolveOptions());
        for (Note note : result.notes) {
            if (note instanceof Note.DroppedTask dropped) {
                System.out.println("task " + dropped.task + ": " + dropped.reason); // task later: unscheduled
            }
        }
    }
}
```

`progress` must be between 0 and 1. A completed task also stays in place inside a summary that moves as a block. In analytics, completed tasks have zero slack and are never critical.

### Scheduling config

[`SchedulingConfig`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md>) holds the project-wide switches. Start from `new SchedulingConfig()` and override only what you need:

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

public class ConfigExample {
    public static void main(String[] args) throws Exception {
        Moment kickoff = Moment.fromYmdHm(2026, 3, 2, 8, 0);

        SchedulingConfig config = new SchedulingConfig()
                .withApplyConstraints(true)            // constraints act as hard bounds
                .withGapBehavior(GapBehavior.COMPRESS) // pull tasks to their earliest
                .withDurationUnit(TimeUnit.HOUR)
                .withProjectStart(kickoff)             // nothing starts before this
                .withCalendarMode(CalendarMode.TASK)   // task calendars only
                .withRootId(Id.num(-1));               // data with a virtual root of -1

        TaskData task = new TaskData(Id.num(1))
                .withParent(Id.num(-1)) // equals rootId: no parent
                .withStartDate(kickoff)
                .withDuration(4.0);

        ProjectData data = new ProjectData()
                .withConfig(config)
                .withTasks(List.of(task));
        Engine.schedule(data, new SolveOptions());
    }
}
```

The switches you'll change most often:

| Field | Default | Effect |
|---|---|---|
| `applyConstraints` | `false` | Turns on the eight constraint types. Off, constraints are ignored, including `MSO` pins |
| `scheduleFromEnd` | `false` | Backward (ALAP) planning. [`level`](<https://docs.dhtmlx.com/planning-engine/java/api/project-level.md>) refuses such projects |
| `gapBehavior` | `PRESERVE` | `PRESERVE` keeps tasks at their own dates when links allow; `COMPRESS` pulls every task to its earliest position |
| `moveProjects` | `true` | Summaries move as a block; `false` schedules their leaves one by one |
| `useProgress` | `false` | Tasks with `progress == 1` are completed and fixed |
| `projectStart` | `null` | Lower bound for the plan, and the date source for undated tasks in repair mode |
| `projectEnd` | `null` | Upper bound, used by backward projects |
| `durationUnit` | `DAY` | Unit of `duration` and `lag` |
| `useCalendars` | `true` | `false` makes every calendar flat 24/7 time |
| `calendarMode` | `RESOURCE` | Which calendars place the work; `TASK` / `TASK_INHERITED` use task calendars only |
| `maxIterations` | `8` | Planner pass limit; must be at least 1 |
| `rootId` | [`Id.num(0)`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md#num>) | The `parent` value that means "no parent" |

`projectConstraint`, `groupMode`, `descendantLinks`, `strictCalendar` and `defaultResourceCapacity` cover rarer cases. See [`SchedulingConfig`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md>) for all of them.

`rootId` exists for data whose virtual root isn't `0`. The engine clears it on import, so stored tasks never point at the root. [`ProjectData#canonicalizeRoots()`](<https://docs.dhtmlx.com/planning-engine/java/api/data/project-data.md#canonicalizeroots>) does the same on your own object. With a non-zero `rootId`, a task with id `0` is an ordinary parent.

Left out, `useCalendars` is on and Planning Engine schedules on calendars. Set it explicitly when your data shouldn't use them.

### Validation rules

The default [`Validation.STRICT`](<https://docs.dhtmlx.com/planning-engine/java/api/project/validation.md>) mode rejects input it would have to coerce or guess at. The call throws an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) with a `path` to the field, and a live [`Project`](<https://docs.dhtmlx.com/planning-engine/java/api/project/project.md>) keeps its previous state and cached result. `InputException` is unchecked, so catch it where you want to report the error.

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

public class StrictExample {
    public static void main(String[] args) throws Exception {
        Moment monday = Moment.fromYmdHm(2026, 3, 2, 8, 0);

        TaskData ok = new TaskData(Id.str("ok"))
                .withStartDate(monday)
                .withDuration(1.0);

        TaskData broken = new TaskData(Id.str("broken"))
                .withDuration(2.0); // only one of start / end / duration

        ProjectData data = new ProjectData().withTasks(List.of(ok, broken));

        try {
            Engine.schedule(data, new SolveOptions());
            System.out.println("scheduled");
        } catch (InputException e) {
            System.out.println((e.path != null ? e.path : "-") + ": " + e.getMessage());
        }
    }
}
```

Strict import rejects:

| Input | Error path |
|---|---|
| Duplicate task, link, resource or calendar id | `tasks[i].id`, `links[i].id`, `resources[i].id`, `calendars[i]` |
| A leaf with fewer than two of start, end and duration | `tasks[i].start_date` |
| Start, end and duration that don't agree | `tasks[i].duration` |
| `endDate` before `startDate` | `tasks[i].end_date` |
| A summary with only one date, or start + duration | `tasks[i].start_date` |
| A summary with no dates and no dated children | `tasks[i]` |
| A milestone with no date | `tasks[i].start_date` |
| A negative or non-finite duration | `tasks[i].duration` |
| A dated constraint without `constraintDate` | `tasks[i].constraint_date` |
| An unknown `parent`, or a cycle in the hierarchy | `tasks[i].parent` |
| `progress` outside 0..1, `priority` above 1000, negative `levelingDelay` | `tasks[i].progress`, `.priority`, `.leveling_delay` |
| Unsorted, overlapping or invalid `pauses` | `tasks[i].pauses[j]` |
| An unknown task `calendarId` | `tasks[i].calendar_id` |
| A calendar with no working time | `calendars[i]` |
| `worktime.days` with other than 7 entries | `calendars[i].worktime.days` |
| A non-finite link `lag` | `links[i].lag` |
| `maxIterations = 0` | `config.max_iterations` |

Paths use the JSON field names, so the same path points at the right field whether the data came from `withX` setters or from JSON. Resource and assignment rules (capacity, `value`, slices, calendars with no shared working time) are in [Resources and assignments](<https://docs.dhtmlx.com/planning-engine/java/resources-and-assignment/resources-and-assignments.md>).

Some things aren't errors. Links that can't apply become [`Note.DroppedLink`](<https://docs.dhtmlx.com/planning-engine/java/api/results/note.md#droppedlink>), unscheduled tasks become [`Note.DroppedTask`](<https://docs.dhtmlx.com/planning-engine/java/api/results/note.md#droppedtask>), and dependency cycles are reported in [`cycles`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#cycles>).

When the data comes from a UI that saves messy records, switch to repair mode instead of fixing the input yourself:

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

public class RepairExample {
    public static void main(String[] args) throws Exception {
        Moment monday = Moment.fromYmdHm(2026, 3, 2, 8, 0);

        TaskData first = new TaskData(Id.str("a"))
                .withStartDate(monday)
                .withDuration(1.0);

        // Same id again: in repair mode the last record wins.
        TaskData second = new TaskData(Id.str("a"))
                .withStartDate(monday)
                .withDuration(3.0);

        Project project = new Project(new ProjectOptions().withValidation(Validation.REPAIR));
        ProjectData data = new ProjectData().withTasks(List.of(first, second));

        ScheduleResult result = project.solve(data, new SolveOptions());
        for (Note note : result.notes) {
            if (note instanceof Note.Repaired repaired) {
                System.out.println(repaired.path + ": " + repaired.message);
            }
        }
    }
}
```

Repair deduplicates ids (the last record wins and keeps the first position), derives missing dates and durations, lets dates win in an inconsistent triple, and clears dated constraints without a date. Each fix shows up as a [`Note.Repaired`](<https://docs.dhtmlx.com/planning-engine/java/api/results/note.md#repaired>) with its path. A task with no dates gets `config.projectStart`, or the earliest date of another task. When neither exists, as in a fully undated project, repair fails at `tasks[i].start_date`. See [Accept messy input](<https://docs.dhtmlx.com/planning-engine/java/how-to/accept-messy-input.md>) and [Error handling](<https://docs.dhtmlx.com/planning-engine/java/error-handling.md>).
