[Canonical page](<https://docs.dhtmlx.com/planning-engine/java/how-to/import-from-dhtmlxgantt/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Import from dhtmlxGantt

Load a dhtmlxGantt project into Planning Engine and get the same task dates the gantt auto-scheduler would give you.

Planning Engine's scheduling step is a port of the dhtmlxGantt v2 auto-scheduling engine, and its records follow gantt's fields: `startDate` is gantt's `start_date`, `constraintType` is `constraint_type`, `autoScheduling` is `auto_scheduling`. Most of the import copies fields one to one. The steps below cover the places where a straight copy gives different dates or fails validation.

## Prerequisites

- Your gantt tasks, links, resources and assignments are parsed into your own Java records. The example uses Java records that mirror gantt's JSON fields.
- You know each task's effective calendar, as gantt resolves it.

To read gantt-named JSON straight into `ProjectData`, see [Exchange JSON with a client](<https://docs.dhtmlx.com/planning-engine/java/how-to/exchange-json-with-a-client.md>). The steps below apply to the values either way.

## Steps

1. Set the scheduling config to gantt's model. Use [`CalendarMode.TASK`](<https://docs.dhtmlx.com/planning-engine/java/api/data/calendar-mode.md>) for task calendars only, or `CalendarMode.TASK_INHERITED` if gantt has `inherit_calendar: true`. Copy gantt's `work_time` into [`useCalendars`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#usecalendars>) explicitly. It defaults to `true`, so Planning Engine schedules on calendars unless you turn it off:

   ```java
   boolean ganttWorkTime = true; // gantt `config.work_time`

   var config = new SchedulingConfig()
       .withCalendarMode(CalendarMode.TASK)
       .withUseCalendars(ganttWorkTime);
   ```

   Set [`rootId`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#rootid>) only when gantt's virtual root isn't `0`. The other auto-scheduling options (`applyConstraints`, `gapBehavior`, `moveProjects`, `scheduleFromEnd`, `projectStart`, `useProgress`) are gantt's options of the same name, in camelCase.

2. Add one [`CalendarData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/calendar-data.md>) per calendar your tasks use, and set each task's `calendarId` to its effective calendar. gantt picks that calendar in its adapter: the task's own, else its resource's, else a calendar merged from all its resources, else the parent's. Send the result, not the rules. A merged resource calendar goes in once, under its synthetic id. Don't repeat gantt's precedence or merging in your import code.

3. Map each task to a [`TaskData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md>):
   - gantt `type: "project"` becomes [`TaskType.SUMMARY`](<https://docs.dhtmlx.com/planning-engine/java/api/data/task-data.md#tasktype>). A summary takes both `startDate` and `endDate`, or neither. Drop its `duration`, or strict validation rejects the task.
   - A `parent` equal to `rootId` means "no parent". You can copy it as is.
   - A leaf task needs two of `startDate`, `endDate` and `duration`. If you send all three, they must agree.
   - If your gantt event handlers cancel auto-scheduling for a task, set `autoScheduling` to `false` on it. The planner never moves it.

4. Map gantt link `type` values `"0"`, `"1"`, `"2"` and `"3"` to [`LinkType.FS`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md#linktype>), `SS`, `FF` and `SF`. Copy `lag` and leave `lagUnit` as `null`, because gantt measures lag in `config.duration_unit`.

5. Convert each assignment to a slice of its task's work in [`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/assignment-data.md>). Don't send gantt's `mode`.
   - `default` and `fixedDuration`: `delay` becomes `offset` and `duration` stays `duration`. Leave both `null` to book the whole task.
   - `fixedDates`: set `offset` and `duration` to the worked time its dates cover, measured from the task start.

   A slice ends at the task end. gantt's `fixedDuration` can run past it.

6. Solve the data with [`Project#solve`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>) and write the changed fields from `result.updates` back into gantt. The updates include summaries, which the gantt JS adapter leaves to gantt core. Apply those too, or your summary spans go stale.

The full example below imports a summary with two tasks, a milestone, two links, a part-time calendar and one `default`-mode assignment:

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

public class ImportFromGantt {
  // Records as your server holds them after parsing gantt's JSON.
  record GanttTask(
      int id,
      int parent,
      String type, // gantt `type`
      String startDate,
      String endDate, // null when gantt has none
      double duration,
      String calendarId) {} // effective calendar, already resolved by gantt

  record GanttLink(int id, int source, int target, String type, double lag) {} // `type`: "0".."3"

  record GanttAssignment(
      int id,
      int taskId,
      String resourceId,
      double value,
      Double delay, // `default` and `fixedDuration` modes
      Double duration) {} // `default` and `fixedDuration` modes

  static LinkType linkType(String type) {
    return switch (type) {
      case "0" -> LinkType.FS;
      case "1" -> LinkType.SS;
      case "2" -> LinkType.FF;
      case "3" -> LinkType.SF;
      default -> throw new IllegalArgumentException("unknown link type " + type);
    };
  }

  static TaskData task(GanttTask g) {
    // Moment.parse reads "YYYY-MM-DD HH:MM". Convert other gantt `date_format` output first.
    var t = new TaskData(Id.num(g.id()))
        .withParent(Id.num(g.parent())) // gantt's root (0) means "no parent"
        .withStartDate(Moment.parse(g.startDate()))
        .withCalendarId(Id.str(g.calendarId()));
    switch (g.type()) {
      case "project" -> {
        // A summary takes both dates or neither, never a duration.
        t.taskType = TaskType.SUMMARY;
        if (g.endDate() != null) {
          t.endDate = Moment.parse(g.endDate());
        } else {
          t.startDate = null;
        }
      }
      case "milestone" -> {
        t.taskType = TaskType.MILESTONE;
        t.duration = 0.0;
      }
      default -> t.duration = g.duration();
    }
    return t;
  }

  public static void main(String[] args) throws Exception {
    var ganttTasks = List.of(
        new GanttTask(1, 0, "project", "2026-03-02 00:00", "2026-03-07 00:00", 5, "global"),
        new GanttTask(2, 1, "task", "2026-03-02 00:00", null, 3, "global"),
        new GanttTask(3, 1, "task", "2026-03-02 00:00", null, 4, "part-time"),
        new GanttTask(4, 0, "milestone", "2026-03-02 00:00", null, 0, "global"));
    var ganttLinks = List.of(
        new GanttLink(1, 2, 3, "0", 0),
        new GanttLink(2, 1, 4, "0", 0));
    var ganttAssignments = List.of(new GanttAssignment(1, 3, "dev", 1.0, 1.0, 2.0));

    var config = new SchedulingConfig()
        .withCalendarMode(CalendarMode.TASK) // gantt's calendar model
        .withUseCalendars(true); // gantt `config.work_time`

    var calendars = List.of(
        new CalendarData().withId(Id.str("global")),
        new CalendarData()
            .withId(Id.str("part-time"))
            .withWorktime(new Worktime()
                // Index 0 is Sunday: Mon-Thu.
                .withDays(new boolean[] {false, true, true, true, true, false, false})));

    var tasks = new ArrayList<TaskData>();
    for (var g : ganttTasks) tasks.add(task(g));

    var links = new ArrayList<LinkData>();
    for (var g : ganttLinks) {
      links.add(new LinkData()
          .withId(Id.num(g.id()))
          .withSource(Id.num(g.source()))
          .withTarget(Id.num(g.target()))
          .withLinkType(linkType(g.type()))
          .withLag(g.lag())); // lagUnit stays null: gantt lag is in `config.duration_unit`
    }

    var assignments = new ArrayList<AssignmentData>();
    for (var g : ganttAssignments) {
      assignments.add(new AssignmentData()
          .withId(Id.num(g.id()))
          .withTask(Id.num(g.taskId()))
          .withResource(Id.str(g.resourceId()))
          .withValue(g.value())
          .withOffset(g.delay())
          .withDuration(g.duration()));
    }

    var data = new ProjectData()
        .withConfig(config)
        .withCalendars(calendars)
        .withTasks(tasks)
        .withLinks(links)
        .withResources(List.of(new ResourceData().withId(Id.str("dev"))))
        .withAssignments(assignments);

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

    for (var m : result.updates) {
      // Only changed fields are set. Summaries are included: write them back too.
      if (m.startDate != null) System.out.println(m.id + " start_date = " + m.startDate);
      if (m.endDate != null) System.out.println(m.id + " end_date = " + m.endDate);
    }
  }
}
```

## Keep gantt's triggers on your side

Planning Engine has no UI triggers and no events. Your code decides when to schedule.

- Call [`apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>) and then [`solve(null, ...)`](<https://docs.dhtmlx.com/planning-engine/java/api/project-solve.md>) wherever gantt would run auto-scheduling: after the user adds a link, drags a task, or saves the lightbox. See [Editing a project](<https://docs.dhtmlx.com/planning-engine/java/live-projects/editing-a-project.md>).
- `gantt.autoSchedule(id, inclusive)` is [`SolveOptions.subtree`](<https://docs.dhtmlx.com/planning-engine/java/api/project/solve-options.md#subtree>). See [Reschedule a branch](<https://docs.dhtmlx.com/planning-engine/java/how-to/reschedule-a-branch.md>).
- Change sets can't add or edit calendars. If an assignment change gives a task a new merged calendar, send the whole project again with `solve(data, ...)`.
- gantt core rewrites descendant dates when inherited calendars or effort change. Forward those date and duration edits as ordinary [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/task-update.md>) changes. Planning Engine doesn't recreate gantt's effort rules.

## Differences to expect

- Strict validation rejects input that gantt coerces: duplicate ids, a dated constraint with no `constraintDate`, a leaf task with fewer than two of start, end and duration, a dateless summary with no dated children. The rejection is an [`InputException`](<https://docs.dhtmlx.com/planning-engine/java/api/errors/input-exception.md>) with the field path. To get gantt's coercions with a report of each fix, see [Accept messy input](<https://docs.dhtmlx.com/planning-engine/java/how-to/accept-messy-input.md>).
- [`TimeUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/time-unit.md>) has no `week`, `month` or `year`, and calendars with `customWeeks` are rejected. A gantt merged calendar can contain `customWeeks`, so such projects can't be imported yet.
- A [`Moment`](<https://docs.dhtmlx.com/planning-engine/java/api/data/moment.md>) is a naive local date-time with no timezone and no DST. gantt's JS engine uses the host time zone, so its arithmetic follows DST. Convert dates to local wall-clock time before you build the data.
- [`result.notes`](<https://docs.dhtmlx.com/planning-engine/java/api/results/schedule-result.md#notes>) lists the links, tasks and assignments the engine dropped. gantt drops them silently.
- Three cases give different dates on purpose. With `projectConstraint = true`, children inherit the parent's constraint. gantt's engine ignores it. With `useProgress`, a completed child of a moving summary stays in place. gantt drags it along. With `descendantLinks = true` in block mode, gantt fails with a cycle error and Planning Engine schedules the project. See [Links, constraints and summaries](<https://docs.dhtmlx.com/planning-engine/java/scheduling/links-constraints-summaries.md>).
- Summary-to-summary links in block mode, combined with leaf links between the same summaries, can give different dates. On long chains of such links the dates grow without bound. See [Error handling](<https://docs.dhtmlx.com/planning-engine/java/error-handling.md>).

## Result

The example prints the changed fields:

```text
3 start_date = 2026-03-05 00:00
3 end_date = 2026-03-12 00:00
1 end_date = 2026-03-12 00:00
4 start_date = 2026-03-12 00:00
4 end_date = 2026-03-12 00:00
```

Task `3` starts when task `2` finishes and works Monday to Thursday only, so it ends a week later. The summary `1` grows to cover it. Task `2` didn't move, so it isn't listed. To get every date on the first load, see [Load all task dates](<https://docs.dhtmlx.com/planning-engine/java/how-to/load-all-task-dates.md>).
