UpdateHandler#

com.palmyralabs.palmyra.handlers.UpdateHandler

Update lifecycle. Composes PreProcessor and a rollback failure hook. Each hook additionally receives the pre-image dbTuple so you can diff new vs. stored values.

Request / response — User#

Assuming the User model from QueryHandler → Worked example:

Request#

The URL carries the primary key (see @CrudMapping{id}); the body carries the new values.

POST /api/v1/admin/user/102
Content-Type: application/json
{
  "firstName": "Ada",
  "lastName":  "Lovelace-Byron",
  "status":    "ACTIVE"
}

Partial payloads are fine — Palmyra merges supplied fields onto the row identified by {id}. Omitted attributes are left untouched.

Response#

The post-update row:

{
  "id": 102,
  "loginName": "ada@example.com",
  "firstName": "Ada",
  "lastName":  "Lovelace-Byron",
  "status":    "ACTIVE",
  "tenantId":  3,
  "createdAt": "2026-04-01T09:12:00Z",
  "createdBy": "admin@example.com"
}

Methods#

Method Signature
validate void validate(Tuple tuple, HandlerContext ctx)
preProcessRaw Tuple preProcessRaw(Tuple tuple, HandlerContext ctx)
preProcess Tuple preProcess(Tuple tuple, HandlerContext ctx)
aclCheck int aclCheck(Tuple tuple, HandlerContext ctx)
getAcl int getAcl(Tuple tuple, HandlerContext ctx)
preUpdate Tuple preUpdate(Tuple tuple, Tuple dbTuple, HandlerContext ctx)
onUpdate Tuple onUpdate(Tuple tuple, Tuple dbTuple, HandlerContext ctx)
postUpdate Tuple postUpdate(Tuple tuple, Tuple dbTuple, HandlerContext ctx)
rollback default Tuple rollback(Tuple tuple, Tuple dbTuple, HandlerContext ctx)

Example#

@Component
@CrudMapping(value = "/v1/admin/user", type = User.class)
public class UserUpdateHandler implements UpdateHandler {

    @Autowired
    private UserProvider userProvider;
    @Autowired
    private OutboxService outbox;

    @Override
    public int aclCheck(Tuple tuple, HandlerContext ctx) {
        return userProvider.hasRole("USER_UPDATE") ? 0 : -1;
    }

    @Override
    public Tuple preUpdate(Tuple tuple, Tuple dbTuple, HandlerContext ctx) {
        tuple.set("updatedBy", userProvider.getUserId());
        tuple.set("updatedAt", Instant.now());
        return tuple;
    }

    @Override
    public Tuple onUpdate(Tuple tuple, Tuple dbTuple, HandlerContext ctx) {
        // detect email change and re-hash downstream identifiers
        if (!Objects.equals(tuple.get("loginName"), dbTuple.get("loginName"))) {
            tuple.set("loginHash", hash((String) tuple.get("loginName")));
        }
        return tuple;
    }

    @Override
    public Tuple postUpdate(Tuple tuple, Tuple dbTuple, HandlerContext ctx) {
        outbox.publish("user.updated", Map.of(
            "id", tuple.get("id"),
            "before", dbTuple,
            "after", tuple
        ));
        return tuple;
    }

    @Override
    public Tuple rollback(Tuple tuple, Tuple dbTuple, HandlerContext ctx) {
        outbox.discardPending(tuple.get("id"));
        return tuple;
    }
}