Library / SDK
instancio/instancio avatar
instancio/instancio

Instancio: random test data that is reproducible when a test fails

A library that creates fully populated objects for your unit tests.

1,170 stars69 forksJavaApache-2.0

At a glance

What is it?
Instancio is a Java library that populates objects for unit tests, so a test can ask for a fully populated Person instead of assembling one field by field. The point is not the convenience but the seed: the random data can be reproduced when a test fails, and the library ships separate modules for JUnit, Guava, Kotlin, Protobuf and a BOM.
Who is it for?
Instancio fits a Java codebase with rich domain objects where hand-written fixtures have become the bottleneck, and it fits especially well where a failure needs to be reproduced exactly, since the seed is the deliverable. It does not fit a team that needs deterministic, semantically meaningful data, because random generation cannot know what a valid customer is, only what the type of customer looks like.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The one-liner replaces a block of setters, and that is the whole pitch

Instancio is a Java library that automatically creates and populates objects for unit tests, and the README opens with the contrast it is selling.

The manual way is a block of construction and setters:

java
Address address  = new Address();
address.setStreet("street");
address.setCity("city");
//...

Person person = new Person();
person.setFirstName("first-name");
person.setLastName("last-name");
person.setAge(22);
person.setGender(Gender.MALE);
person.setAddress(address);
//...

And the Instancio way is one line:

java
Person person = Instancio.create(Person.class);

That returns a fully populated person, including nested objects and collections. The savings are obvious for a class with ten fields, and they compound: a test suite with a hundred domain objects and a shared fixture builder ends up with a hundred builders to maintain, and every new field breaks them all.

The second half of the value proposition is the one that matters more in practice. The object is populated with random data that can be reproduced in case of test failure. That is the difference between this and a convenience library. A test that fails intermittently with random input is a test you cannot debug, and the standard answer, seed the random generator and log it, is exactly what this library does for you, which means the failure message can point at a seed and the data can be rebuilt exactly.

The library is Apache-2.0 licensed, published to Maven Central as `org.instancio:instancio-core` and `org.instancio:instancio-junit`, with the current release at 6.1.0 from 2026-09-26 and the last push on 2026-10-19.

Customisation is per field, and the API is built around selectors

The customisation API is where the library earns its keep, and its shape comes from how you address a field.

java
Person person = Instancio.of(Person.class)
    .generate(field(Person::getDateOfBirth), gen -> gen.temporal().localDate().past())
    .generate(field(Phone::getAreaCode), gen -> gen.oneOf("604", "778"))
    .generate(field(Phone::getNumber), gen -> gen.text().pattern("#d#d#d-#d#d-#d#d"))
    .subtype(all(AbstractAddress.class), AddressImpl.class)
    .supply(all(LocalDateTime.class), () -> LocalDateTime.now())
    .onComplete(all(Person.class), (Person p) -> p.setName(p.getGender() == Gender.MALE ? "John" : "Jane"))
    .create();

The addressing is by field reference, `field(Person::getDateOfBirth)`, which is type safe and survives a rename of the class, unlike a string path. The `all(...)` form is a selector covering every instance of a type, which is how the subtype, supply and onComplete rules above apply to every `AbstractAddress`, every `LocalDateTime` and every `Person` in the graph.

The generator API is a namespace per type, so `gen.temporal().localDate().past()` for dates, `gen.text().pattern(...)` for a formatted string, `gen.oneOf(...)` for a fixed set, and `gen.ints().range(40, 50)` for a numeric range. That last one is not a general random integer, it is a range, and a library that offers ranges rather than leaving you to filter and retry is doing real work for you.

The three escape hatches are worth distinguishing. `subtype` fixes an abstract type to a concrete implementation. `supply` replaces the generator entirely with a lambda, which is how you inject a fixed value for a type that has no sensible random form. And `onComplete` runs after the object graph is built, which is the only place you can set a field that depends on another field, and the example shows exactly that, deriving a name from a gender.

Models are the reusable form, and they are what a team actually wants

The single most useful feature in the list is the one that comes fifth in the README's numbering, because it is the difference between a library you call in every test and a library you configure once.

java
Model<Person> simpsons = Instancio.of(Person.class)
    .set(field(Person::getLastName), "Simpson")
    .set(field(Address::getCity), "Springfield")
    .generate(field(Person::getAge), gen -> gen.ints().range(40, 50))
    .toModel();

Person homer = Instancio.of(simpsons)
    .set(field(Person::getFirstName), "Homer")
    .create();

Person marge = Instancio.of(simpsons)
    .set(field(Person::getFirstName), "Marge")
    .create();

A `Model` is a template, and the same template is then instantiated with a per-test override. The shared conventions, the last name, the city, the age range, live in one place, and each test overrides only what it cares about. That is the shape of a test fixture factory, except the factory is a value you can pass around and reuse across test classes, and it is defined with the same field-based API as everything else.

The other three creation shapes in the README are simpler. `Instancio.ofList(Person.class).size(10).create()` creates a list of a given size, `Instancio.stream(Person.class).limit(5)` creates a stream, and `Instancio.create(new TypeToken<Pair<List<Foo>, List<Bar>>>() {})` handles generic types, where the type argument has to be captured at runtime because erasure would otherwise lose it.

There is also data feed support, for values that live in an external file, with `feed.ofResource("persons.csv")` for a plain resource and a `@Feed.Source` interface for a reusable, typed feed with templates and function providers. The example shows a feed spec with a `${firstName} ${lastName}` template and a `@FunctionSpec` that computes a bio from the name and date of birth, which is a way of expressing realistic data without hand-maintaining a CSV.

A JUnit extension wires generation into the test method itself

The JUnit integration is what makes the library usable without a builder call in every test, and it has two distinct shapes.

The first is argument injection. With `InstancioExtension` registered through `@ExtendWith`, a test method can declare parameters and have them generated:

java
@ExtendWith(InstancioExtension.class)
class ExampleTest {

    @Given
    private List<String> randomList;

    @Test
    void example1(@Given String randomSting, @Given(SomeCustomProvider.class) customString) {
        //...
    }

    @ValueSource(strings = {"foo", "bar"})
    @ParameterizedTest
    void example2(String fooOrBar, @Given long randomLong) {
        // ...
    }
}

The `@Given` annotation marks a parameter or field as generated, and `@Given(SomeCustomProvider.class)` brings in a custom provider. The `@ParameterizedTest` example is the interesting one, because it mixes a `@ValueSource` parameter, which is fixed by the test author, with a `@Given` parameter, which is random. So a test can hold one value constant and vary the other across runs, without writing the loop.

The second shape is repetition, which is the more interesting idea:

java
@ExtendWith(InstancioExtension.class)
class ExampleTest {

    @RepeatedTest(100)
    void example(UUID randomUuid, Foo randomFoo, @Given(BarProvider.class) Bar customBar) {
        // runs the test method 100 times against generated inputs
    }
}

A `@RepeatedTest(100)` against generated arguments runs the body a hundred times with different random inputs each time. That is a form of property testing that costs one annotation, and it finds bugs that a fixed fixture would sit on top of for ever. The UUID parameter here is an ordinary parameter rather than an annotated one, which suggests the extension resolves unannotated parameters too.

The main features list also records support for generics, `record` and `sealed` classes, Java 21 sequenced collections, generation based on JPA and Bean Validation annotations from Hibernate and jakarta, flexible configuration options and SPIs, support for defining custom generators, and population via fields and setters.

Maven coordinates, and an artifact you should not use

The dependency choice is a small decision with a trap in it.

If you have JUnit on the classpath, use the `instancio-junit` dependency:

xml
<dependency>
    <groupId>org.instancio</groupId>
    <artifactId>instancio-junit</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
</dependency>

To use Instancio standalone, with TestNG or other test frameworks, use `instancio-core` instead, with the same group id and the same `test` scope. And to manage the different dependencies, there is a BOM, `instancio-bom`, imported through `dependencyManagement` with `pom` type and `import` scope.

The trap is called out in a caution block at the end of that section. The `org.instancio:instancio` artifact on Maven Central is an older dependency that should no longer be used. A one-word difference in the artifact id, `instancio` rather than `instancio-core`, and a project that used the old coordinates would still compile, which is exactly the kind of thing that goes unnoticed.

The repository layout shows the module structure behind those coordinates. There is `instancio-core/`, `instancio-junit/`, `instancio-bom/`, and then the optional integrations `instancio-guava/`, `instancio-kotlin/` and `instancio-protobuf/`. So the Guava, Kotlin and Protobuf support is opt-in through separate modules rather than baked into the core, which keeps the core dependency footprint small. There is also `instancio-tests/`, `instancio-benchmark/`, `build-tools/`, `website/` for the user guide, `ide/` and `etc/`.

The `justfile` shows how the project is built. `just all` runs `mvn clean install`, there are targets for the core module and the two test suites, a `skip-plugins` target that turns off PMD, CPD, Checkstyle and javadoc, a `javadoc` target, and a `benchmark` target that builds a JMH jar and runs it, writing the results to a timestamped file. The release target runs `mvn release:clean release:prepare` and `release:perform`, and prints the link to the Sonatype central publishing page.

Where Instancio is the wrong tool

The first case is a test that needs semantically meaningful data rather than structurally valid data. Instancio produces a `Person` with a random age, a random name and a random phone number, and it can be told to make the age fall in a range or the name depend on a gender. It cannot know that this particular customer is the one from the failing bug report, and it cannot know that the address is a London address rather than a random one. A test that asserts on business logic involving specific values still needs those values, and a test double with a stubbed repository is a better fit for that than a random object.

The second case is a team that has already invested in a builder pattern. Many Java codebases have their own test data builders, often generated by a library such as a builder generator, and they are not wrong. If your domain objects have required invariants that a builder enforces, a builder gives you a compile error when you forget a field, whereas Instancio will happily produce an object that violates an invariant unless you told it about the constraint through the Bean Validation or JPA annotations. The library supports both annotation-driven generation and manual rules, so the question is whether the rules you already have are worth keeping.

The third case is a codebase with deeply recursive or self-referential structures without a sensible depth. Generation has to stop somewhere, and the default depth is a small number. A tree structure of unbounded depth needs its limits configured, and a configuration file with hundreds of overrides is a sign that the models are wrong rather than the generator.

One practical caution about the reproducibility claim. It holds per test run, and it depends on the seed being reported. The library does that, but it means a failing test in continuous integration produces a seed you have to feed back in to reproduce, and if the object graph contains something non-deterministic, such as a timestamp taken from the clock, the reproduction is not exact. The `supply(all(LocalDateTime.class), () -> LocalDateTime.now())` example in the README is exactly that case, and it is a reminder that injecting the clock defeats reproducibility.

Editorial conclusion

Instancio fits a Java codebase with rich domain objects where hand-written fixtures have become the bottleneck, and it fits especially well where a failure needs to be reproduced exactly, since the seed is the deliverable. It does not fit a team that needs deterministic, semantically meaningful data, because random generation cannot know what a valid customer is, only what the type of customer looks like. Before adopting, read the section on models and templates, because a reusable Model is where most of the value is, and take the `instancio-junit` artifact rather than the older `org.instancio:instancio` one, which the documentation marks as no longer to be used.

Frequently asked questions

What is Instancio and what does it replace?

Instancio is a Java library that automatically creates and populates objects for unit tests, replacing blocks of construction and setter calls with a single call such as `Instancio.create(Person.class)`, which returns a fully populated object including nested objects and collections.

Which Maven dependency should I use for Instancio?

Use `org.instancio:instancio-junit` if you have JUnit on the classpath, and `org.instancio:instancio-core` for standalone use, with TestNG or other frameworks. The `instancio-bom` manages the versions. The plain `org.instancio:instancio` artifact is an older dependency that should no longer be used.

How do I make Instancio generate specific values?

Use the `generate` call with a field reference and a generator, for example `generate(field(Phone::getAreaCode), gen -> gen.oneOf("604", "778"))`. There are also `subtype` to fix an abstract type, `supply` to replace the generator with a lambda, and `onComplete` to set a field after the object graph is built.

What is a Model in Instancio and why would I use one?

A Model is a reusable template of an object, built with `toModel()`, holding the shared settings such as fixed values and ranges. You then create instances from it with `Instancio.of(model).set(...).create()` and override only what each test needs, which keeps common test conventions in one place.

Official sources

  1. instancio/instancio on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/instancio-instancio.svg)](https://hysenlabs.com/projects/instancio-instancio)

Was this useful?