[Canonical page](<https://docs.dhtmlx.com/planning-engine/java/api/changes/link-update/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# LinkUpdate

A partial edit of one existing link. Only the fields set to a patch other than `Patch.missing()` change; the rest keep their current values. Add it to `changes.links.update` of a [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/scheduling-changes.md#links>) and pass the change set to [`Project#apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>), [`Project#preview`](<https://docs.dhtmlx.com/planning-engine/java/api/project-preview.md>) or [`Project#evaluate`](<https://docs.dhtmlx.com/planning-engine/java/api/project-evaluate.md>). Create one with `new LinkUpdate(id)` and the fluent `withX` setters.

## Declaration

```java
public class LinkUpdate extends ValueRecord implements DeepCloneable<LinkUpdate>
```

## Constructors

### LinkUpdate(Id)

```java
public LinkUpdate(Id id)
```

Creates an update for the link with this id. Every other field is `Patch.missing()`, so the update changes nothing until a field is set.

**Parameters**

| Name | Type | Description |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) | Id of the link to edit. Build it with `Id.str("a")` or `Id.num(1)`. |

The class has no parameterless constructor.

## Fields

Every field except `id` is a [`Patch<T>`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/patch.md>) and starts as `Patch.missing()`. `Patch.of(value)` sets the field. The last column shows what `Patch.nullValue()` does. The fields mean the same as on [`LinkData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md>).

| Field | Type | `Patch.nullValue()` |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/java/api/data/id.md>) | Not a patch. Selects the link to edit. |
| `source` | `Patch<Id>` | Rejected: a link always has a source. |
| `target` | `Patch<Id>` | Rejected: a link always has a target. |
| `linkType` | `Patch<`[`LinkType`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md#linktype>)`>` | Resets to `LinkType.FS`. |
| `lag` | `Patch<Double>` | Resets to `0.0`. |
| `lagUnit` | `Patch<`[`TimeUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/time-unit.md>)`>` | Clears the unit, so `lag` is read in [`SchedulingConfig.durationUnit`](<https://docs.dhtmlx.com/planning-engine/java/api/data/scheduling-config.md#durationunit>). |

### id

```java
public Id id
```

Id of an existing link. The update edits that link and can't change its id.

### source, target

```java
public Patch<Id> source = Patch.missing();
public Patch<Id> target = Patch.missing();
```

A value moves that end of the link to another task. If the new task doesn't exist or is unscheduled, the update is still applied, but the link is dropped from scheduling and its id is listed in `ApplyReport.effects.droppedLinks` (see [`ApplyReport`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/apply-report.md#changeeffects>)).

`Patch.of(null)` on either field throws `NullPointerException` when the change set is applied.

### linkType

```java
public Patch<LinkType> linkType = Patch.missing();
```

Which end of `source` drives which end of `target`.

### lag

```java
public Patch<Double> lag = Patch.missing();
```

Working time between the two ends, in the link's lag unit. Negative values are allowed. The value must be finite. `Patch.of(null)` throws `NullPointerException` when the change set is applied.

### lagUnit

```java
public Patch<TimeUnit> lagUnit = Patch.missing();
```

Unit of `lag` for this link only.

## Methods

### withId, withSource, withTarget, withLinkType, withLag, withLagUnit

```java
public LinkUpdate withId(Id value)
public LinkUpdate withSource(Patch<Id> value)
public LinkUpdate withTarget(Patch<Id> value)
public LinkUpdate withLinkType(Patch<LinkType> value)
public LinkUpdate withLag(Patch<Double> value)
public LinkUpdate withLagUnit(Patch<TimeUnit> value)
```

Each setter assigns its field and returns this instance. The patch setters take a `Patch`, not a plain value: write `withLag(Patch.of(2.0))`.

**Returns** - this `LinkUpdate`.

### deepClone

```java
public LinkUpdate deepClone()
```

Returns a new `LinkUpdate` with the same field values. `Patch` and `Id` are immutable, so the copy shares nothing that can change.

**Returns** - a new `LinkUpdate`.

### copy

```java
public LinkUpdate copy()
```

Returns a shallow copy with the same field values.

**Returns** - a new `LinkUpdate`.

### equals, hashCode, toString

```java
public boolean equals(Object other)
public int hashCode()
public String toString()
```

Inherited from [`ValueRecord`](<https://docs.dhtmlx.com/planning-engine/java/api/support/value-record.md>). Two `LinkUpdate` are equal when all six fields are equal; patches compare by kind and value. `toString()` lists the fields, for logging.

## Errors

An invalid update doesn't throw. [`Project#apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>) lists it in `ApplyReport.rejected` as a [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/rejected-operation.md>) with `path` `"links.update[i]"`, and the link stays as it was. An update is rejected when:

- `id` names no link in the project: `unknown link id`
- `source` or `target` is `Patch.nullValue()`: `required field cannot be null`
- `lag` is `NaN` or infinite: `lag must be finite`

A rejection sets `ApplyReport.needsResync`. See [`Project#apply`](<https://docs.dhtmlx.com/planning-engine/java/api/project-apply.md>) for what that means for later calls.

## Example

Turn link `"a"` into a start-to-start link with a two-hour lag and leave its tasks as they are:

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

public class EditLink {
  public static void main(String[] args) throws Exception {
    Moment monday = Moment.fromYmdHm(2026, 4, 6, 8, 0);
    ProjectData data = new ProjectData()
        .withTasks(List.of(
            new TaskData(Id.num(1)).withStartDate(monday).withDuration(2.0),
            new TaskData(Id.num(2)).withStartDate(monday).withDuration(1.0)))
        .withLinks(List.of(
            new LinkData().withId(Id.str("a")).withSource(Id.num(1)).withTarget(Id.num(2))));

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

    SchedulingChanges changes = new SchedulingChanges().withBaselineId(solved.baselineId);
    changes.links.update.add(new LinkUpdate(Id.str("a"))
        .withLinkType(Patch.of(LinkType.SS))
        .withLag(Patch.of(2.0))
        .withLagUnit(Patch.of(TimeUnit.HOUR)));

    ApplyReport report = project.apply(changes);
    for (RejectedOperation rejected : report.rejected) {
      System.out.println(rejected.path + ": " + rejected.message);
    }

    ScheduleResult result = project.solve(null, options);
    for (Mutation update : result.updates) {
      System.out.println("task " + update.id + ": " + update.startDate + " .. " + update.endDate);
    }
  }
}
```

## Related

- [`LinkData`](<https://docs.dhtmlx.com/planning-engine/java/api/data/link-data.md>) - the stored link and its fields
- [`LinkAdd`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/link-add.md>) - adding a new link
- [`Patch<T>`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/patch.md>) - the three-state type of every editable field
- [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/java/api/changes/rejected-operation.md>) - how a rejected update is reported
- [Data Updates](<https://docs.dhtmlx.com/planning-engine/java/live-projects/editing-a-project.md#update-records-and-patch>) - update records in a live project
