# jFairy: locale aware fake data with classpath overridable dictionaries

> jFairy is a Java library that generates people, companies and identifiers in thirteen locales, with its data shipped as properties files you can override from your own classpath. It suits teams that need realistic test fixtures in several languages, and it will frustrate anyone expecting rich, relational fake datasets.

**SkillPanel/jfairy** — Java fake data generator

- Repository: https://github.com/SkillPanel/jfairy
- Website: https://skillpanel.github.io/jfairy/
- Stars: 745 · Forks: 150
- Language: Java
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/skillpanel-jfairy

## A Fairy object that produces one domain object at a time

jFairy is a Java fake data generator published under the group id com.devskiller, with the artifact id jfairy and the current version 0.9.0. The entry point is a Fairy instance created through a static factory method, and each call on it returns a domain object rather than a record in a bulk dataset.

The basic usage is short enough to read in one go:

```java
Fairy fairy = Fairy.create();
Person person = fairy.person();

System.out.println(person.getFirstName());
// Chloe
System.out.println(person.getLastName());
// Barker
System.out.println(person.getEmail());
// barker@yahoo.com
System.out.println(person.getTelephoneNumber());
// 690-950-802
System.out.println(person.getJobTitle());
// Software Developer
```

The objects are consistent with each other. A telephone number, an email address and a job title belong to the same person, and the email is derived from the name rather than being a random string. The library also supports properties on the person, so a specific age or sex can be requested:

```java
Person adultMale = fairy.person(PersonProperties.male(), PersonProperties.minAge(21));
System.out.println(adultMale.isMale());
// true
System.out.println(adultMale.getDateOfBirth());
// at least 21 years earlier
```

That is the shape of the library. It is an object factory with a fluent property API, not a dataset builder.

## Related objects share the same company reference

The second feature worth understanding is that the library can wire objects together instead of generating them independently.

```java
Fairy fairy = Fairy.create();
Company company = fairy.company();
System.out.println(company.getName());
// Robuten Associates
System.out.println(company.getUrl());
// http://www.robuteniaassociates.com

Person salesman = fairy.person(PersonProperties.withCompany(company));
System.out.println(salesman.getFullName());
// Juan Camacho
System.out.println(salesman.getCompanyEmail());
// juan.camacho@robuteniaassociates.com
```

The point is that the person's company email is built from the company name, and the person carries the same company instance the caller already holds. If a test creates a customer, an invoice and a contract, all three can reference one company object, which is the kind of referential consistency that a naive random generator gets wrong.

This is still a small feature set compared with a full data generation platform. There is no notion of a database, no bulk insertion, no schema, and no notion of generating a population with distributions across fields. The library's scope is the domain object graph, one object at a time, with the relations between them done by hand.

## Thirteen locales selected at construction time

Locale support is a first class part of the API rather than a bolt-on. The Fairy factory accepts a Locale, and the supported list covers English as the default, plus Polish, German, French, Spanish, Swedish, Chinese, Georgian, Italian, Brazilian Portuguese, Slovak, Turkish and Japanese.

The language tags used in the table are not all standard country codes. Polish is pl, German de, French fr, Spanish es, Swedish sv, Chinese zh, Georgian ka, Italian it, Slovak sk, Turkish tr and Japanese ja, while Brazilian Portuguese is listed with the tag br rather than pt-BR. That detail matters if you are selecting a locale programmatically, because Locale.forLanguageTag with a non standard tag will not fall back to a country specific variant the way a region qualified tag would.

```java
Fairy enFairy = Fairy.create();
// Locale.ENGLISH is default
Fairy plFairy = Fairy.create(Locale.forLanguageTag("pl"));
// Polish version
Fairy brFairy = Fairy.create(Locale.forLanguageTag("br"));
// Brazilian version
```

The locale is fixed at construction, so switching language means creating another Fairy. The data files follow the same split, with one properties file per language, which is what makes a Polish test fixture produce Polish names, a Polish tax identifier and a Polish address without any per-call configuration.

## Overriding the bundled dictionaries from your own classpath

The data is not compiled into the code. The bundled dataset is maintained as YAML in the repository and ships in the jar as properties files, jfairy.properties plus one jfairy_<language>.properties per language. The override mechanism is a plain classpath rule: put a file with the same name on your classpath, and every key it defines replaces the bundled one, while keys it leaves out keep the bundled data.

```properties
# src/test/resources/jfairy_en.properties
cities=Springfield, Shelbyville
firstNames.male=Homer,Bart
```

The semantics are precise enough to plan around. Lists are comma separated, spaces around elements are trimmed, and empty elements are skipped. Data split by sex uses .male and .female sub-keys, so overriding firstNames.male keeps the bundled female names, whereas a plain key such as lastNames=Simpson replaces the list for every sex. Files are read as UTF-8.

Weights are supported per list element. With lastNames=Nowak*98387,Kowalski*66589,Adamiec, Nowak comes up about 98387 times as often as Adamiec, since an element without a weight counts as 1. Weights are positive whole numbers and relative within a single list, and a list with no weights is picked uniformly. A malformed weight fails when the key is first used, naming both the file and the key, which means a typo in a test resource surfaces at the point of use rather than at startup.

If your data lives under a different name, the builder lets you change the prefix. Calling Fairy.builder().withFilePrefix("mydata") makes the library read mydata.properties and mydata_<language>.properties instead. One upgrade caveat is stated plainly: custom .yml data files are no longer read when upgrading from 0.9.x, so they have to be converted to properties.

## Unique values and deliberately invalid identifiers

Two features exist for the awkward cases that most generators skip.

The first is uniqueness. A UniqueFairy wraps a Fairy and guarantees that the objects it returns differ on a chosen key:

```java
UniqueFairy unique = fairy.unique();
Person p1 = unique.person();  // unique by email
Person p2 = unique.person();  // different email than p1
Company c = unique.company(); // unique by name
```

For a custom key, a UniqueEnforcer takes a supplier and a key extractor:

```java
UniqueEnforcer<Person> unique = UniqueEnforcer.of(fairy::person, Person::getFullName);
Person p = unique.next();
```

The second is the opposite of realism on purpose. For negative testing of validators, fairy.invalid() returns identifiers that keep a valid format but fail checksum validation:

```java
Fairy fairy = Fairy.create(Locale.forLanguageTag("pl"));
InvalidFairy invalid = fairy.invalid();

String pesel = invalid.nationalIdentificationNumber(); // wrong check digit
String nip = invalid.vatIdentificationNumber();        // wrong check digit
IBAN iban = invalid.iban();                            // wrong check digits
```

Getting a person to carry an invalid national identification number requires passing the same birth date and sex to both the number and the person, so that only the checksum is wrong:

```java
LocalDate birthDate = LocalDate.of(1990, 5, 17);
String pesel = invalid.nationalIdentificationNumber(
    NationalIdentificationNumberProperties.dateOfBirth(birthDate),
    NationalIdentificationNumberProperties.sex(Person.Sex.FEMALE));

Person person = fairy.person(
    PersonProperties.female(),
    PersonProperties.withDateOfBirth(birthDate),
    PersonProperties.withNationalIdentificationNumber(pesel));
```

The limitation is narrow and worth knowing: invalid national identification and VAT numbers are implemented for the Polish, Slovak and Swedish locales, and other locales throw UnsupportedOperationException. IBANs work for every country that has them.

## One Fairy per thread, and the JUnit 5 extension

The thread safety section is short and unambiguous. Fairy objects are not designed for concurrent use by multiple threads, and the recommendation is to create a separate Fairy instance for each thread. The reasoning given is that while some methods might appear safe, the underlying Random implementation can lead to contention and poor performance.

That is a design decision with a real cost in a parallel test suite. A team running tests on a thread pool needs a Fairy per worker, or a thread local, and a shared static instance will be a source of both wrong results and slow tests. The documentation is at least honest that the failure mode is contention rather than a hard crash.

For JUnit 5 users there is a separate project, jfairy-junit-extension, which is an extension that allows injecting jFairy-generated objects directly into test method parameters. It lives in its own repository rather than in this one, so it has its own version cycle.

The build itself is Maven based, with a wrapper committed at the root:

```bash
./mvnw
```

The repository also carries the usual project files: a pom.xml at the root, a .mvn directory, a .sdkmanrc for the JDK version, an .editorconfig, a CODE_OF_CONDUCT.md, a CONTRIBUTING.md and a build directory. The artifacts are published to a public repository, with a badge pointing at the Sonatype Central listing for com.devskiller/jfairy, and javadoc is published as well.

## Where jFairy is the wrong tool

Several cases rule this library out.

The first is bulk data seeding. If a test needs a thousand customers in a database with realistic distributions, jFairy has no answer for it. There is no bulk API, no way to write a set of objects straight to a store, and no notion of a schema. The per-object design that makes the library pleasant for unit tests is exactly what makes it unsuitable for populating a database.

The second is domain depth. People, companies, telephone numbers, emails, job titles, addresses and identifiers are the visible surface. There is no order, no product, no payment method, no realistic quantity or price distribution, and no way to plug in your own entity types without writing them against the library's internals.

The third is non Java consumers. The topics list mentions Groovy and Android, and the library is published to Maven Central for JVM use, but there is no documented way to drive it from a Python test suite that needs the same fixture data. A polyglot team ends up with two separate fixture sources, and the risk is that they drift apart.

The fourth is the dictionary override model itself. It is a nice mechanism, but it only replaces list values. It cannot change the shape of an object, add a field, or alter how a value is derived, so a project whose data needs differ structurally from the bundled ones will find itself fighting the library rather than configuring it.

## Conclusion

Adopt jFairy when your tests need a person, a company or a national identifier that looks real in one of the thirteen supported locales, and when you want to override the bundled dictionaries from your own test resources rather than patching the library. Do not adopt it as a bulk data seeding tool or a database fixture engine, since the API produces individual objects and the dictionaries are flat lists. Before you start, note that upgrading from 0.9.x means converting custom YAML data files to properties, that invalid national identification and VAT numbers exist only for the Polish, Slovak and Swedish locales, and that a Fairy instance must not be shared between threads.

## FAQ

### How do I add jFairy to a Maven project?

Add the com.devskiller groupId with the jfairy artifactId and version 0.9.0 to your pom.xml. The library is published to a public repository and javadoc is available for the artifact.

### Which locales does jFairy support?

Thirteen: English as the default, plus Polish, German, French, Spanish, Swedish, Chinese, Georgian, Italian, Brazilian Portuguese, Slovak, Turkish and Japanese. The locale is set when the Fairy instance is created with Fairy.create(Locale.forLanguageTag(...)).

### How do I override the fake data in jFairy?

Put a properties file with the bundled name on your classpath, such as jfairy_en.properties. Every key it defines replaces the bundled one and the keys it leaves out keep the bundled data, with lists comma separated and .male or .female sub-keys for data split by sex.

### Is jFairy safe to share between threads?

No. Fairy objects are not designed for concurrent use by multiple threads, and the recommendation is to create a separate Fairy instance for each thread, since the underlying Random implementation can cause contention and poor performance.

### Can jFairy generate invalid identifiers for negative tests?

Yes, through fairy.invalid(), which produces identifiers with a valid format but a wrong checksum, including national identification numbers, VAT numbers and IBANs. Invalid national identification and VAT numbers are implemented for the Polish, Slovak and Swedish locales, and other locales throw UnsupportedOperationException.

## Sources

- [License: Apache-2.0](https://github.com/SkillPanel/jfairy/blob/master/LICENSE)
- [Project website](https://skillpanel.github.io/jfairy/)
- [README](https://github.com/SkillPanel/jfairy/blob/master/README.md)
- [Releases](https://github.com/SkillPanel/jfairy/releases)
- [SkillPanel/jfairy on GitHub](https://github.com/SkillPanel/jfairy)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/skillpanel-jfairy
