KVKK-Compliant Software Design: Privacy by Design in Turkey
Compliance with Turkey's data protection law (KVKK, closely aligned with GDPR) is achieved by design, not by a privacy notice. How the personal-data inventory maps to code, data minimization and field-level encryption, retention periods and automated deletion, access logs, data-subject requests and breach notification, with Spring Boot examples.
In most organizations, compliance with Turkey's data protection law (KVKK, Law No. 6698) is treated as the legal department's job: a privacy notice is written, the registry filing is made, clauses are added to contracts. The software stays as it is, on the assumption that "we already protect the data". Then a data-subject request arrives and nobody knows which tables, which backups and which log files hold the customer's data; or a breach happens and what leaked can't be reported within 72 hours.
KVKK compliance — closely aligned with GDPR, and known there as privacy by design — is achieved through the software's design decisions. This article translates the law's principles into technical patterns: the personal-data inventory reflected in code, data minimization, field-level encryption and masking, retention periods and automated deletion, access logs, data-subject requests and breach notification. Examples are in Spring Boot; consult your KVKK specialist for legal assessment, here we cover the architecture.
From principle to pattern
KVKK principle / obligation | Technical counterpart |
|---|---|
Lawful processing for a specific purpose | Data inventory: purpose, legal basis and retention period for every personal-data field; marked in code and schema |
Limited and proportionate to the purpose (minimization) | Don't collect unnecessary fields; return only needed fields on screens and in APIs; anonymous/pseudonymous analytics |
Accurate and up to date | A flow where the data subject can see their own data and request correction |
Retained only as long as necessary | Retention-period field + periodic deletion job (at least every six months); deletion record |
Data security (technical and administrative measures) | Role-based authorization, field-level encryption, access log, penetration testing, key management |
Data-subject rights (requests, 30 days) | A "data subject" service that finds, exports and deletes all data of one person |
Breach notification (to the Board within 72 hours) | Access and export logs; the ability to query which data was affected |
International transfer (GDPR-like regime since the 2024 amendment) | Data-residency decision; region settings of cloud and SaaS components; transfer inventory |
1. The inventory lives in code
A personal-data inventory kept in an Excel file goes stale with the first release. Move the inventory into code: every personal field is marked with its classification, purpose and retention period; the inventory report is generated from those marks.
@Entity
public class Customer {
@Id private Long id;
@PersonalData(category = Category.IDENTITY, purpose = "contract performance", retentionMonths = 120)
private String fullName;
@PersonalData(category = Category.CONTACT, purpose = "contract performance", retentionMonths = 120)
@Convert(converter = EncryptedFieldConverter.class) // field-level encryption
private String phone;
@PersonalData(category = Category.SPECIAL, purpose = "legal obligation", retentionMonths = 120)
@Convert(converter = EncryptedFieldConverter.class)
private String nationalId;
}// Inventory report: generated from code, never updated by hand
List<InventoryRow> inventory = entityManager.getMetamodel().getEntities().stream()
.flatMap(e -> Arrays.stream(e.getJavaType().getDeclaredFields()))
.filter(f -> f.isAnnotationPresent(PersonalData.class))
.map(InventoryRow::from)
.toList();The second benefit of this approach: "which tables contain personal data" becomes a query; at request or breach time, the list is at hand.
2. Minimization: data you don't collect can't leak
In forms: If there is no answer to "why is this needed" for a field, the field is removed. If an "over 18" confirmation is enough, don't collect the date of birth.
In APIs and screens: List screens return masked values (05** *** 12 34); the full value only for an authorized role, through an explicit "reveal" action, logged.
In analytics: Reports and the data warehouse work with pseudonymous ids; the identity-mapping table is separate and access-restricted.
In test environments: Production data is never copied as is; anonymized or synthetic data is used.
3. Encryption and masking
Disk encryption (database TDE, cloud disk encryption) is the mandatory baseline but not sufficient: anyone with database access sees the data in plain text. For sensitive fields, field-level encryption happens in the application layer; the key is kept in a key management system (KMS, Vault) separate from the database and rotated.
@Converter
public class EncryptedFieldConverter implements AttributeConverter<String, String> {
private final FieldCipher cipher; // AES-256-GCM; key from KMS, with a version tag
@Override public String convertToDatabaseColumn(String plain) { return plain == null ? null : cipher.encrypt(plain); }
@Override public String convertToEntityAttribute(String encrypted) { return encrypted == null ? null : cipher.decrypt(encrypted); }
}Encrypted fields can't be searched; if searching by national id is required, an HMAC digest (search key) is written to the same row and matching is done on the digest. Logs are a separate matter: personal data must never enter a log line; masking (pattern-based for phone, e-mail, national id) is set up as a central filter in the logging configuration.
4. Retention periods and automated deletion
The "retain only as long as necessary" principle cannot be applied without a date field and a scheduled job. The retention period for every personal-data set is defined in the inventory; when it expires the data is deleted, anonymized or destroyed and a deletion record is kept (the regulation requires periodic deletion at least every six months).
@Scheduled(cron = "0 0 3 1 * *") // 1st of every month, 03:00
void periodicDeletion() {
for (RetentionRule r : retentionRules.active()) { // derived from the inventory
List<DeletionCandidate> candidates = r.expired(clock.instant());
for (DeletionCandidate c : candidates) {
switch (r.method()) {
case DELETE -> c.delete();
case ANONYMIZE -> c.anonymize(); // name → "***", phone → null, id → hash
}
deletionRecords.save(DeletionRecord.of(c, r, clock.instant())); // for audit
}
}
}Two difficulties: backups (deleted data persists in backups; backup retention is part of the deletion policy, and destroying the encryption key — crypto-shredding — is a practical solution) and conflicting legal retention (invoice data is kept for 10 years under commercial law; even if the customer says "delete", the invoice stays, only marketing data is deleted). The rule engine resolves this conflict from the legal basis in the inventory.
5. Access log: who saw what, and when?
Every access to personal data — especially full-value viewing, exports and bulk queries — is written to an immutable log with who, when, which record and for what purpose. This log is the answer to the "affected persons" question at breach time and a deterrent to internal misuse. In practice it's done with a service-layer aspect or at the API gateway; the log contains no personal data, only record ids.
6. Data-subject requests: 30 days, one service
When a person says "what data do you hold on me, delete it", the answer must come within 30 days. Doing this manually depends on whoever scans the fifteen tables and three external systems. The right design is a data-subject service that finds all personal data from a person's id, exports it in readable form and applies deletion/anonymization; thanks to the inventory marks, which tables to scan is known from the code. For data passed to external systems (e-mail provider, CRM SaaS), the deletion request propagates automatically to their APIs.
7. International transfer and component choice
With the 2024 amendment, the international-transfer regime moved closer to GDPR: an adequacy decision, appropriate safeguards such as standard contracts, or exceptions are required. On the software side this means evaluating every cloud and SaaS component (e-mail delivery, error tracking, analytics, LLM APIs, CDN) by where it processes data and keeping a transfer inventory. A Turkey/EU region where the service allows it; pseudonymous data or a no-use decision where it doesn't.
Implementation checklist
Personal fields are marked in code with category, purpose and retention; the inventory is generated from code.
Forms and APIs collect and return only needed fields; lists are masked.
Special-category and identity data are field-level encrypted; the key is in a KMS and rotatable.
A log filter masks personal data; no national ids, phones or e-mails in logs.
Retention periods are defined; the periodic deletion job runs and keeps deletion records; the backup policy is aligned.
An access log for personal data exists; affected records can be queried at breach time.
Data-subject service: find, export, delete/anonymize; propagates to external systems.
Test environments run on anonymized/synthetic data.
A data-residency and transfer inventory exists for cloud and SaaS components.
Frequently asked questions
Does the difference between KVKK and GDPR affect the software?
The technical patterns are largely the same; the differences are in deadlines (30 days for requests, 72 hours for breach notification), administrative obligations such as the VERBİS registry and the details of the transfer regime. For organizations operating in both Turkey and the EU, one design satisfies both regimes.
What is the difference between anonymization and pseudonymization?
Anonymous data cannot be reversed and is not personal data; pseudonymous data can be linked back to a person through a mapping table and remains personal data. Pseudonymous for analytics, anonymous for deletion.
Can this be applied to an existing system?
Yes, gradually: first inventory marks and log masking (low risk, high gain), then the access log and the data-subject service, and last field-level encryption (which requires a data migration) and deletion jobs.
Conclusion
KVKK compliance is an architecture in which the inventory lives in code, unnecessary data isn't collected, sensitive fields are encrypted, retention is enforced automatically, access is logged and one person's data can be managed through one service. Once this architecture is in place, compliance stops being a project and becomes the system's natural behavior.
To assess your existing application against KVKK from a technical standpoint and apply these patterns gradually, see our software design and architecture and enterprise application development services.