TechnitiumSoftware/DnsServer · error · InvalidOperationException

Old and new record types do not match.

Error message

Old and new record types do not match.

What it means

Thrown by ForwarderZone.UpdateRecord() in the default case when oldRecord.Type != newRecord.Type. UpdateRecord implements record mutation as a delete-then-add with shared commit/serial, and allowing a type change there would corrupt the per-type _entries dictionary and the SOA serial bookkeeping. DNS updates that change a record's type are not an update; they are a delete of one type plus an add of another, so the API enforces type identity. This is a programming-contract violation (InvalidOperationException), not a transient or data condition.

Source

Thrown at DnsServerCore/Dns/Zones/ForwarderZone.cs:247

                        TriggerNotify();

                        return true;
                    }

                    return false;
            }
        }

        public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
        {
            switch (oldRecord.Type)
            {
                case DnsResourceRecordType.SOA:
                    throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record");

                default:
                    if (oldRecord.Type != newRecord.Type)
                        throw new InvalidOperationException("Old and new record types do not match.");

                    if (newRecord.OriginalTtlValue > GetZoneSoaExpire())
                        throw new DnsServerException("Cannot update record: TTL cannot be greater than SOA EXPIRE.");

                    if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))
                        throw new DnsServerException("Cannot update record: the record does not exists to be updated.");

                    AddRecord(newRecord, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);

                    List<DnsResourceRecord> allDeletedRecords = new List<DnsResourceRecord>(deletedRecords.Count + 1);
                    allDeletedRecords.Add(deletedRecord);
                    allDeletedRecords.AddRange(deletedRecords);

                    CommitAndIncrementSerial(allDeletedRecords, addedRecords);

                    TriggerNotify();
                    break;
            }

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Keep the record type immutable across an update: construct newRecord with the same DnsResourceRecordType as oldRecord.
  2. If a type change is genuinely required, perform zone.DeleteRecord(oldType, oldRdata) then zone.AddRecord(newRecord) as two operations instead of UpdateRecord.
  3. Validate type equality in your API layer and return a 400 before calling UpdateRecord.

Example fix

// before
var newRecord = new DnsResourceRecord(name, pickedType, ...);
zone.UpdateRecord(oldRecord, newRecord);

// after
var newRecord = new DnsResourceRecord(name, oldRecord.Type, ...); // keep type
zone.UpdateRecord(oldRecord, newRecord);
Defensive patterns

Strategy: validation

Validate before calling

if (oldRecord.Type != newRecord.Type)
    throw new ArgumentException("Record type cannot change on update; delete+add instead.");
zone.UpdateRecord(oldRecord, newRecord);

Type guard

static bool IsTypeCompatibleUpdate(DnsResourceRecord oldRecord, DnsResourceRecord newRecord) => oldRecord.Type == newRecord.Type;

Try / catch

try { zone.UpdateRecord(oldRecord, newRecord); }
catch (InvalidOperationException ex) when (ex.Message.Contains("types do not match")) { /* perform delete(oldType) + add(newType) instead */ }

Prevention

When it happens

Trigger: Calling zone.UpdateRecord(oldRecord, newRecord) where oldRecord is e.g. an A record and newRecord is an AAAA record (or any type mismatch). Common when a client builds newRecord from form input that lets the user change the record-type dropdown, or when copying fields between record DTOs without preserving Type.

Common situations: Edit-record UI with a mutable type selector that was not disabled for edits; DTO mapping that omits Type and defaults it; refactoring that swaps record constructors.

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/1f3b7b8f51be22fb. Report an issue: GitHub.