Skip to content

Why you should use Instancio in 2026

Published: at 05:00 AM

Why you should use Instancio in 2026

Stop wasting time writing test data boilerplate

If you’ve written Java tests for more than a week, you know the pain. You want to test a simple validation method, but first you need to write 30 lines of test data setup. You create builders, manually set fields, nest objects inside objects, and by the time you’re done, you’ve forgotten what you were trying to test in the first place.

There has to be a better way.

Enter Instancio - a library that eliminates test data boilerplate by automatically generating realistic test objects. It’s been around since 2022, but if you haven’t heard of it yet, you’re in for a treat.

What is Instancio?

Instancio is a Java library that uses reflection to automatically populate your objects with random, sensible data. Instead of manually constructing test objects, you simply tell Instancio what type you need and it handles the rest.

Think of it as the opposite of a mocking framework. While Mockito helps you create fake dependencies, Instancio helps you create real test data.

The library is actively maintained, has excellent documentation at https://www.instancio.org, and works seamlessly with JUnit 5, TestNG, and any other testing framework.

The Problem: Test Data Setup Tax

Let’s look at a real-world example. Suppose we have a UserService that processes user registrations. We want to test the email validation logic.

Here’s what the domain model looks like:

public class User {
    private UUID id;
    private String firstName;
    private String lastName;
    private String email;
    private Address address;
    private List<PhoneNumber> phoneNumbers;
    private boolean active;
    private LocalDateTime createdAt;
    private UserPreferences preferences;
}

public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;
    private String country;
}

public class PhoneNumber {
    private String number;
    private PhoneType type;
}

public class UserPreferences {
    private boolean emailNotifications;
    private boolean smsNotifications;
    private String timezone;
    private String language;
}

Now here’s what a typical test looks like WITHOUT Instancio:

@Test
void shouldRejectInvalidEmail() {
    // Setup - The old, painful way
    Address address = new Address();
    address.setStreet("123 Main St");
    address.setCity("New York");
    address.setState("NY");
    address.setZipCode("10001");
    address.setCountry("USA");

    PhoneNumber phone = new PhoneNumber();
    phone.setNumber("555-1234");
    phone.setType(PhoneType.MOBILE);

    UserPreferences preferences = new UserPreferences();
    preferences.setEmailNotifications(true);
    preferences.setSmsNotifications(false);
    preferences.setTimezone("America/New_York");
    preferences.setLanguage("en");

    User user = new User();
    user.setId(UUID.randomUUID());
    user.setFirstName("John");
    user.setLastName("Doe");
    user.setEmail("invalid-email");  // This is what we actually care about!
    user.setAddress(address);
    user.setPhoneNumbers(List.of(phone));
    user.setActive(true);
    user.setCreatedAt(LocalDateTime.now());
    user.setPreferences(preferences);

    // Finally, the actual test...
    ValidationResult result = userService.validateEmail(user);

    assertThat(result.isValid()).isFalse();
    assertThat(result.getErrors()).contains("Invalid email format");
}

That’s 35 lines of setup code just to test email validation. And if the User class changes? You’ll need to update every single test.

Here’s the same test WITH Instancio:

@Test
void shouldRejectInvalidEmail() {
    // Setup - The Instancio way
    User user = Instancio.of(User.class)
        .set(field(User::getEmail), "invalid-email")
        .create();

    // The actual test
    ValidationResult result = userService.validateEmail(user);

    assertThat(result.isValid()).isFalse();
    assertThat(result.getErrors()).contains("Invalid email format");
}

That’s it. 3 lines instead of 35. Instancio automatically populates all the other fields with sensible random data. The test is now focused on what matters - the email validation logic.

Getting Started

Add Instancio to your test dependencies:

dependencies {
    testImplementation 'org.instancio:instancio-junit:4.3.0'
}

Or if you’re using Maven:

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

Now you’re ready to start generating test data.

Basic Usage

The simplest way to use Instancio is with Instancio.create():

// Creates a User with all fields populated
User user = Instancio.create(User.class);

// Creates a list of 5 users
List<User> users = Instancio.ofList(User.class).size(5).create();

When you need to customize specific fields, use Instancio.of():

User user = Instancio.of(User.class)
    .set(field(User::getEmail), "test@example.com")
    .set(field(User::isActive), true)
    .create();

Instancio handles primitives, Strings, dates, UUIDs, enums, collections, and nested objects automatically. It generates:

Custom Generators and Selectors

Sometimes you need more control over the generated data. Instancio provides powerful generators and selectors for this.

Using Built-in Generators

Instancio includes generators for common patterns:

User user = Instancio.of(User.class)
    .generate(field(User::getEmail), gen -> gen.text().pattern("[a-z]{5,10}@example.com"))
    .generate(field(User::getFirstName), gen -> gen.string().minLength(3).maxLength(10))
    .generate(field(User::getCreatedAt), gen -> gen.temporal().localDateTime().past())
    .create();

Generating Specific Values from a Set

Need to restrict values to a specific set?

User user = Instancio.of(User.class)
    .generate(field(Address::getCountry), gen -> gen.oneOf("USA", "Canada", "Mexico"))
    .generate(field(User::isActive), gen -> gen.booleans().probability(0.8)) // 80% true
    .create();

Selectors for Targeting Multiple Fields

Instead of targeting a single field, you can target all fields of a certain type:

User user = Instancio.of(User.class)
    .generate(all(String.class), gen -> gen.string().minLength(1))
    .generate(all(LocalDateTime.class), gen -> gen.temporal().localDateTime().past())
    .create();

This is incredibly useful when you want to ensure all strings are non-empty or all dates are in the past.

Custom Suppliers

For complex logic, use suppliers:

User user = Instancio.of(User.class)
    .supply(field(User::getEmail), () -> {
        String randomName = RandomStringUtils.randomAlphabetic(10).toLowerCase();
        return randomName + "@test.com";
    })
    .create();

Working with Collections and Arrays

Instancio makes it trivial to generate collections with specific characteristics.

Lists, Sets, and Maps

// List with exactly 10 users
List<User> users = Instancio.ofList(User.class).size(10).create();

// Set with 3-7 users
Set<User> userSet = Instancio.ofSet(User.class).size(3, 7).create();

// Map with String keys and User values
Map<String, User> userMap = Instancio.ofMap(String.class, User.class)
    .size(5)
    .create();

Controlling Nested Collections

You can control the size of nested collections too:

User user = Instancio.of(User.class)
    .generate(field(User::getPhoneNumbers), gen -> gen.collection().size(2))
    .create();

Arrays

User[] users = Instancio.of(User[].class).length(5).create();

Integration with Test Frameworks

Instancio integrates beautifully with JUnit 5, making your tests even cleaner.

JUnit 5 Extension

The InstancioExtension allows you to inject test data directly into test methods:

@ExtendWith(InstancioExtension.class)
class UserServiceTest {

    @Test
    void shouldProcessValidUser(@Given User user) {
        // User is automatically created by Instancio
        ValidationResult result = userService.validate(user);
        assertThat(result.isValid()).isTrue();
    }

    @Test
    void shouldHandleInactiveUser(@Given User user) {
        // You can still customize injected instances
        user.setActive(false);

        boolean canLogin = userService.canLogin(user);
        assertThat(canLogin).isFalse();
    }
}

Parameterized Tests with Instancio

Combine Instancio with JUnit’s parameterized tests for powerful data-driven testing:

@ExtendWith(InstancioExtension.class)
class UserValidationTest {

    @ParameterizedTest
    @ValueSource(strings = {"", "invalid", "@example.com", "test@"})
    void shouldRejectInvalidEmails(String invalidEmail, @Given User user) {
        user.setEmail(invalidEmail);

        ValidationResult result = userService.validateEmail(user);
        assertThat(result.isValid()).isFalse();
    }
}

Creating Reusable Models

For consistent test data across your test suite, create reusable models:

public class TestModels {

    public static Model<User> activeUser() {
        return Instancio.of(User.class)
            .set(field(User::isActive), true)
            .generate(field(User::getCreatedAt), gen -> gen.temporal().localDateTime().past())
            .toModel();
    }

    public static Model<User> adminUser() {
        return Instancio.of(User.class)
            .set(field(User::isActive), true)
            .set(field(User::getRole), UserRole.ADMIN)
            .toModel();
    }
}

Then use them in your tests:

@Test
void shouldAllowAdminAccess() {
    User admin = Instancio.create(TestModels.adminUser());

    boolean hasAccess = userService.hasAdminAccess(admin);
    assertThat(hasAccess).isTrue();
}

Real-World Example: Testing a REST Controller

Let’s tie everything together with a realistic example - testing a REST controller that handles user creation.

@ExtendWith(InstancioExtension.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldCreateUserSuccessfully(@Given CreateUserRequest request) throws Exception {
        // Customize the generated request
        User expectedUser = Instancio.of(User.class)
            .set(field(User::getEmail), request.getEmail())
            .create();

        when(userService.createUser(any())).thenReturn(expectedUser);

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJson(request)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.email").value(request.getEmail()));
    }

    @Test
    void shouldValidateRequiredFields() throws Exception {
        CreateUserRequest request = Instancio.of(CreateUserRequest.class)
            .set(field(CreateUserRequest::getEmail), null)  // Invalid!
            .create();

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJson(request)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors[*].field").value(hasItem("email")));
    }

    @Test
    void shouldHandleDuplicateEmail(@Given CreateUserRequest request) throws Exception {
        when(userService.createUser(any()))
            .thenThrow(new DuplicateEmailException(request.getEmail()));

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJson(request)))
            .andExpect(status().isConflict());
    }
}

Notice how the tests focus on the behavior being tested, not on setting up test data. Instancio handles the heavy lifting.

Why You Should Use Instancio in 2026

Here’s why Instancio should be in every Java developer’s testing toolkit:

  1. Reduces Boilerplate: Cut test setup code by 70-90%. Spend time writing tests, not setting up data.

  2. Improves Test Maintainability: When domain models change, you don’t need to update dozens of tests. Instancio adapts automatically.

  3. Better Test Coverage: It’s so easy to generate test data that you’ll write more tests. No more skipping tests because the setup is too painful.

  4. Catches Edge Cases: Random data generation often exposes bugs you wouldn’t find with hardcoded test data. Your code might work fine with firstName = "John" but fail with firstName = "José-María".

  5. Forces Better Design: If Instancio struggles to generate your objects, it’s often a sign of overly complex constructors or tight coupling. It encourages simpler, more testable designs.

  6. Excellent Documentation: The docs at https://www.instancio.org are comprehensive, with examples for every feature.

  7. Active Development: Regular updates, responsive maintainers, and a growing community.

Common Pitfall: Complex Third-Party Types

One limitation of Instancio is handling complex third-party types that require specific initialization. For example, geospatial libraries or custom JSON objects might not work out of the box.

The solution? Create an Instancio extension using the Service Provider Interface (SPI). This lets you define custom generators for complex types that are automatically loaded.

I recently wrote about this in detail in my post on Writing an Instancio Extension. If you work with specialized libraries, it’s worth the investment to create reusable generators.

Summary

If you’re still manually creating test data in 2026, you’re working too hard. Instancio eliminates the tedious boilerplate that makes testing painful and lets you focus on what matters - writing good tests.

Start small. Pick one test file and convert it to use Instancio. You’ll immediately see the benefit. Then expand from there.

Your future self (and your teammates) will thank you.

Check out the docs: https://www.instancio.org/getting-started/

The GitHub repo: https://github.com/instancio/instancio


Previous Post
Writing an Instancio Extension Library
Next Post
Part 5: Vibe Coding for Non-Coders