Generate realistic Swedish test data including valid personnummer (personal identity numbers) with Luhn algorithm validation. Perfect for performance testing, development, and QA environments.
- Features
- Quick Start
- API Documentation
- Endpoints
- Swedish Personnummer
- Data Fields
- Configuration
- Deployment
- πΈπͺ Valid Swedish Personnummer - Generated with correct Luhn algorithm validation
- β
Correct Age Formatting - Properly handles 100+ year old persons with
+delimiter - π Multiple Formats - CSV and JSON output formats
- π― Realistic Data - Names, addresses, phone numbers, emails, job titles
- π’ Bulk Generation - Generate up to 25,000 persons at once
- π Real Swedish Postal Codes - Uses actual Swedish postal codes and areas
- π UUID Generation - Generate unique identifiers for testing
- ποΈ SQL Execution - Execute queries for database testing
- π OpenAPI Documentation - Interactive Swagger UI
docker run -d \
-p 8080:8080 \
--name testdatagen \
testdatagen:latestmvn spring-boot:runCSV Format (multiple persons):
curl "http://localhost:8080/RandomPerson?antal=100" -o testdata.csvJSON Format (single person):
curl "http://localhost:8080/RandomPersonJson"Once the application is running, access the interactive API documentation:
- Swagger UI:
http://localhost:8080/swagger-ui/index.html - OpenAPI JSON:
http://localhost:8080/v3/api-docs - Health Check:
http://localhost:8080/healthcheck
Generate multiple Swedish persons with complete profiles in CSV format.
Endpoint: GET /RandomPerson
Parameters:
| Parameter | Type | Required | Range | Description |
|---|---|---|---|---|
antal |
String | β Yes | 1-25000 | Number of persons to generate |
Example Request:
curl "http://localhost:8080/RandomPerson?antal=1000" -o persons.csvExample Response (CSV):
Personnummer;longPersonnummer;namn;efterNamn;Address;postAdress;zip;telefon;mobil;jobPosition;jobTitel;email
591125-3291;195911253291;Lennart;Larsson;Γvre FabriksvΓ€gen 20;Halland;22969;917603114;0735074106;Designer;District Marketing Planner;sjxxic394769@kihtij249753.com
690705-8645;196907058645;Hans;Γnglund;Lennarts VΓ€g 24;SkΓ₯ne;64984;457837500;0731807980;Consultant;Internal Education Consultant;hyyjfj880250@ooxlmk992126.com
241015+1234;192410151234;Astrid;BergstrΓΆm;Storgatan 45;Stockholm;11122;812345678;0701234567;Engineer;Senior Software Engineer;abcdef123456@ghijkl789012.comFeatures:
- β Age range: 17-70 years old
- β Valid personnummer with Luhn algorithm
- β
Correctly formatted with
-or+based on age - β Real Swedish postal codes and areas
- β UTF-8 BOM for Excel compatibility
Generate a single Swedish person with complete profile in JSON format.
Endpoint: GET /RandomPersonJson
Parameters: None
Example Request:
curl "http://localhost:8080/RandomPersonJson"Example Response (JSON):
{
"Personnummer": "850709-1232",
"longPersonnummer": "198507091232",
"namn": "Erik",
"efterNamn": "Andersson",
"Address": "Storgatan 12",
"postAdress": "Stockholm",
"zip": "11122",
"telefon": "0812345678",
"mobil": "0701234567",
"jobPosition": "Developer",
"jobTitel": "Senior Software Developer",
"email": "abcdef123456@ghijkl789012.com",
"kommun": "Stockholms kommun"
}Generate multiple random UUIDs for test data correlation.
Endpoint: GET /RandomUUID
Parameters:
| Parameter | Type | Required | Range | Description |
|---|---|---|---|---|
antal |
String | β Yes | 1-50000 | Number of UUIDs to generate |
Example Request:
curl "http://localhost:8080/RandomUUID?antal=5"Example Response:
f03f3dde-be25-4459-9221-f8740406068b
e1d80578-c9dd-4e8d-90dd-ece6af423a0a
a7b2c3d4-e5f6-4789-a012-b3c4d5e6f789
9f8e7d6c-5b4a-3210-fedc-ba9876543210
1a2b3c4d-5e6f-7890-a1b2-c3d4e5f6a7b8
Execute SQL queries against databases and return results in CSV format.
β οΈ Warning: This endpoint executes raw SQL queries. Use only in test environments with proper security controls.
Endpoint: POST /SQL
Request Body:
{
"jdbc": "jdbc:mysql://localhost:3306/testdb",
"sql": "SELECT * FROM users LIMIT 10",
"user": "dbuser",
"password": "dbpass"
}Example Request:
curl -X POST "http://localhost:8080/SQL" \
-H "Content-Type: application/json" \
-d '{
"jdbc": "jdbc:mysql://localhost:3306/testdb",
"sql": "SELECT id, name, email FROM users LIMIT 5",
"user": "testuser",
"password": "testpass"
}'Example Response (CSV):
ID;NAME;EMAIL
1;John Doe;john@example.com
2;Jane Smith;jane@example.com
3;Bob Johnson;bob@example.comSupported Databases:
- MySQL
- PostgreSQL
- Oracle
- SQL Server
- SQLite
A Swedish personnummer is a unique personal identity number with the format:
YYMMDD-XXXX or YYYYMMDD-XXXX
- YYMMDD/YYYYMMDD: Date of birth (year-month-day)
- XXX: Serial number (second-to-last digit indicates gender)
- Odd: Male
- Even: Female
- X: Control digit (calculated using Luhn algorithm)
CRITICAL: The delimiter indicates age, not century:
| Delimiter | Age | Example |
|---|---|---|
- (dash) |
Under 100 years | 850709-1232 (39 years old in 2024) |
+ (plus) |
100 years or older | 241015+1234 (100 years old in 2024) |
This application correctly implements this format, including the + delimiter for centenarians.
The control digit is calculated using the Luhn algorithm:
- Take all digits except the control digit
- Multiply every other digit by 2 (from right to left)
- If a product > 9, add its digits (e.g., 14 β 1 + 4 = 5)
- Sum all digits
- Round up to nearest 10
- Control digit = (rounded value - sum)
For 850709-123X:
Digits: 8 5 0 7 0 9 1 2 3
Multiply: Γ2 Γ2 Γ2 Γ2 Γ2
Result: 16 5 0 14 0 18 2 2 6
Sum: (1+6)+5+0+(1+4)+0+(1+8)+2+2+6 = 36
Round up to 40
Control digit: 40 - 36 = 4
Final: 850709-1234
| Birth Year | Current Age | Short Format | Long Format |
|---|---|---|---|
| 1924 | 100 | 241015+1234 |
19241015123 |
| 1950 | 74 | 501015-5678 |
19501015567 |
| 1985 | 39 | 850709-1232 |
19850709123 |
| 2005 | 19 | 051201-9876 |
20051201987 |
Each generated person includes the following fields:
| Field | Description | Example |
|---|---|---|
Personnummer |
Short format with delimiter | 850709-1232 |
longPersonnummer |
Long format (12 digits) | 198507091232 |
namn |
First name (Swedish) | Erik |
efterNamn |
Last name (Swedish) | Andersson |
Address |
Street address | Storgatan 12 |
postAdress |
Postal area/region | Stockholm |
zip |
Postal code | 11122 |
telefon |
Landline phone | 0812345678 |
mobil |
Mobile phone | 0701234567 |
jobPosition |
Job position | Developer |
jobTitel |
Job title | Senior Software Developer |
email |
Random email address | abc123@def456.com |
kommun |
Municipality (JSON only) | Stockholms kommun |
Postal codes and areas are loaded from src/main/resources/static/postnummer.csv.
ZIP_CODE;POSTAL_AREA;MUNICIPALITY
293 93;GrΓ€num;OlofstrΓΆms kommun
293 94;OlofstrΓΆm;OlofstrΓΆms kommun
294 07;SΓΆlvesborg;SΓΆlvesborgs kommunThe file contains authentic Swedish postal codes covering all municipalities and regions in Sweden.
Create src/main/resources/application.properties:
# Server configuration
server.port=8080
# Logging
logging.level.root=INFO
logging.level.com.testdatagen=DEBUG
# Spring configuration
spring.main.allow-bean-definition-overriding=false
Ensure postnummer.csv exists in src/main/resources/static/:
src/
βββ main/
βββ resources/
βββ static/
βββ postnummer.csv
FROM openjdk:21-jdk-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]# Build JAR
mvn clean package
# Build Docker image
docker build -t testdatagen:latest .
# Run container
docker run -d \
-p 8080:8080 \
--name testdatagen \
--restart unless-stopped \
testdatagen:latestapiVersion: apps/v1
kind: Deployment
metadata:
name: testdatagen
spec:
replicas: 2
selector:
matchLabels:
app: testdatagen
template:
metadata:
labels:
app: testdatagen
spec:
containers:
- name: testdatagen
image: testdatagen:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /healthcheck
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthcheck
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: testdatagen
spec:
selector:
app: testdatagen
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIPpipeline {
agent any
environment {
TESTDATAGEN_URL = 'http://testdatagen:8080'
}
stages {
stage('Generate Test Data') {
steps {
script {
// Generate 1000 test persons
sh """
curl "${TESTDATAGEN_URL}/RandomPerson?antal=1000" \
-o test-data/persons.csv
"""
// Generate UUIDs for test correlation
sh """
curl "${TESTDATAGEN_URL}/RandomUUID?antal=100" \
-o test-data/uuids.txt
"""
}
}
}
stage('Use Test Data') {
steps {
// Your test execution here
sh 'neoload run --test-data test-data/persons.csv'
}
}
}
post {
always {
archiveArtifacts artifacts: 'test-data/**', allowEmptyArchive: true
}
}
}stages:
- prepare
- test
generate_testdata:
stage: prepare
script:
- curl "http://testdatagen:8080/RandomPerson?antal=5000" -o testdata.csv
- curl "http://testdatagen:8080/RandomUUID?antal=500" -o uuids.txt
artifacts:
paths:
- testdata.csv
- uuids.txt
expire_in: 1 day
performance_test:
stage: test
dependencies:
- generate_testdata
script:
- jmeter -n -t test.jmx -Jtestdata=testdata.csv -l results.jtlname: load_test_with_swedish_data
variables:
- file:
name: swedish_persons
path: testdata.csv
is_first_line_column_names: true
delimiter: ';'
change_policy: each_iteration
scope: local
user_paths:
- name: test_userpath
actions:
steps:
- transaction:
name: create_user
steps:
- request:
url: https://api.example.com/users
method: POST
body: |
{
"personnummer": "${swedish_persons.Personnummer}",
"name": "${swedish_persons.namn} ${swedish_persons.efterNamn}",
"email": "${swedish_persons.email}",
"phone": "${swedish_persons.mobil}"
}#!/bin/bash
# Generate 10,000 test persons
curl "http://localhost:8080/RandomPerson?antal=10000" -o persons.csv
# Import to database
mysql -u root -p testdb << EOF
LOAD DATA LOCAL INFILE 'persons.csv'
INTO TABLE test_users
FIELDS TERMINATED BY ';'
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(personnummer, long_personnummer, first_name, last_name,
address, postal_area, zip, phone, mobile, job_position,
job_title, email);
EOF
echo "Imported 10,000 test persons to database"<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet">
<stringProp name="filename">testdata.csv</stringProp>
<stringProp name="fileEncoding">UTF-8</stringProp>
<stringProp name="delimiter">;</stringProp>
<boolProp name="recycle">true</boolProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
</CSVDataSet>Verify the service is running:
curl http://localhost:8080/healthcheckResponse:
{
"status": "ok",
"service": "API Health Check"
}- π Interactive API Documentation (Swagger UI)
- π OpenAPI JSON Specification
- π GitHub Repository
- πΈπͺ Swedish Personal Identity Numbers (Wikipedia)
- π’ Luhn Algorithm
- π Java Faker Library
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the Apache License 2.0.
- Version: 1.0
- Java Version: 21
- Spring Boot: 3.3.4
- OpenAPI: 3.0
This application correctly implements the Swedish personnummer format:
-(dash): Person under 100 years old+(plus): Person 100 years or older
Many systems incorrectly assume - means 1900s and + means 2000s. This is wrong. The delimiter is based on age, not century.
All generated personnummer are validated using the Luhn algorithm, ensuring they are mathematically valid according to Swedish standards.
This application generates fake test data for development and testing purposes. The personnummer, while mathematically valid, are randomly generated and do not correspond to real people.
Made with β€οΈ for Swedish performance engineers and testers