Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

JUnit Jupiter’s DisplayNameGenerator lets you make test reports easier to scan without adding @DisplayName to every test. For most teams, ReplaceUnderscores is the simplest default: write descriptive method names with underscores, and JUnit displays them as words. Use IndicativeSentences when nested test classes add useful context, and reserve explicit names for exceptions and parameterized-test invocations.

Display names improve navigation and diagnostics in IDEs, reports, and build output. They do not change test execution, assertions, ordering, or behavior. This guide refers to JUnit Jupiter; check your project’s JUnit version before using newer annotations.

The quickest improvement: replace underscores with spaces

Java method names must be identifiers, so descriptive names often end up awkward in reports. With the default generator, a method such as should_return_true_when_user_is_active may appear with underscores and trailing parentheses. ReplaceUnderscores turns those underscores into spaces:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class User_repository {

    @Test
    void finds_a_user_by_id() {
    }

    @Test
    void returns_empty_when_the_user_does_not_exist() {
    }
}

The report can then show a hierarchy like this:

User repository
├─ finds a user by id
└─ returns empty when the user does not exist

The generator only replaces underscores. It does not split camelCase, fix grammar, or infer what a test means. For readable output, write the test method name as a clear behavioral description and separate its words with underscores.

See the JUnit Jupiter DisplayNameGenerator API and the JUnit 5.13.4 user guide for the documented generator behavior.

Choose the generator that fits the suite

Generator What it displays Best fit
Standard JUnit Jupiter’s normal naming behavior. Exact formatting depends on the method signature and test context. Keep the usual default.
Simple Like Standard, but removes trailing parentheses from no-argument methods. Keep camelCase identifiers while dropping empty parentheses.
ReplaceUnderscores Replaces underscores with spaces. Use descriptive, underscore-separated Java identifiers as readable labels.
IndicativeSentences Combines enclosing-class and method names into a contextual, sentence-like display name. Show meaningful context from nested test classes.

Simple is a small cosmetic change, not a prose generator. For example, a method called shouldReturnActiveAccount is displayed without its empty parentheses, but remains camelCase. With Standard, a typical no-argument method may be displayed as shouldReturnActiveAccount().

For most teams, ReplaceUnderscores is the most useful starting point: it is predictable and adds little ceremony. Choose IndicativeSentences when class nesting expresses conditions or domain context that readers need. The available built-ins and their behavior are documented in the JUnit API reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Apply a generator locally

Put @DisplayNameGeneration on a test class when that class follows a consistent naming convention. The annotation applies to types and is inherited through superclasses and implemented interfaces; nested test classes also inherit it from their enclosing class. Check the annotation API for its scope and inheritance rules.

import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class Shopping_cart {

    @Test
    void adds_an_item_to_the_cart() {
    }

    @Test
    void rejects_an_item_that_is_unavailable() {
    }
}

Use a base class or test interface to establish a convention only when you intend its descendants to inherit it. For nested tests, setting the generator on the outer class usually keeps the hierarchy consistent. Avoid mixing generators across a project without a clear reason.

Set a project-wide default

To use the same generator throughout a test project, create src/test/resources/junit-platform.properties and add:

junit.jupiter.displayname.generator.default = 
  org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores

The property value is the generator’s fully qualified class name. The built-in generators are nested inside DisplayNameGenerator, so the configuration uses $ between the outer class and generator name. In Java annotation syntax, use the nested-class form with .class instead:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)

A global default is a project convention, not a replacement for choosing names carefully. A class-level @DisplayNameGeneration takes precedence over it. The property and precedence rules are covered in the JUnit user guide.

Know which name wins

For a class or test method, the practical precedence is:

  1. An explicit @DisplayName on that class or method.
  2. An applicable @DisplayNameGeneration on the class hierarchy.
  3. The junit.jupiter.displayname.generator.default project setting.
  4. DisplayNameGenerator.Standard when no other choice applies.

That means an explicit @DisplayName can make a generator appear not to work. If you rename a method and its report label stays the same, look for an explicit display name; it intentionally overrides generated naming.

Parameterized tests have an additional naming layer

A generator names the test method or template, but the name attribute on @ParameterizedTest controls the labels for individual invocations. Configure both when a report needs a readable hierarchy:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class Password_validation {

    @ParameterizedTest(name = "Input "{0}" is valid: {1}")
    @CsvSource({
        "'abc123', true",
        "'short', false"
    })
    void validates_password_strength(String input, boolean expected) {
    }
}

A report might show the class and method as containers, with invocation labels such as Input "abc123" is valid: true and Input "short" is valid: false. The generator does not replace the parameterized-test name pattern. Keep patterns focused on useful input and expected-result details rather than dumping large object representations.

Use nested tests for context

Nested classes can make conditions explicit while keeping each method name short. With ReplaceUnderscores, the test tree is already useful:

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class Order_service {

    @Nested
    class When_the_order_exists {

        @Test
        void returns_the_order() {
        }
    }

    @Nested
    class When_the_order_does_not_exist {

        @Test
        void returns_an_empty_result() {
        }
    }
}

The hierarchy can read as:

Order service
├─ When the order exists
│  └─ returns the order
└─ When the order does not exist
   └─ returns an empty result

To combine enclosing class and method fragments into a continuous label, use @IndicativeSentencesGeneration:

@IndicativeSentencesGeneration(
    separator = " -> ",
    generator = DisplayNameGenerator.ReplaceUnderscores.class
)
class Order_service {

    @Nested
    class When_the_order_exists {

        @Test
        void returns_the_order() {
        }
    }
}

This can produce a label such as Order service -> When the order exists -> returns the order. The annotation’s defaults use ", " as the separator and Standard as its fragment generator. Choose a separator your report viewer handles well. Full sentences can be more readable, but long repeated context is harder to scan in CI output. See the IndicativeSentencesGeneration API and IndicativeSentences API.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use explicit fragments for wording that identifiers cannot express

JUnit Jupiter 5.13.0 introduced @SentenceFragment, which supplies custom text for an individual fragment generated by IndicativeSentences. For example:

@IndicativeSentencesGeneration(
    separator = " -> ",
    generator = DisplayNameGenerator.ReplaceUnderscores.class
)
class Checkout {

    @Nested
    @SentenceFragment("the payment is declined")
    class Payment_is_declined {

        @Test
        void shows_the_retry_option() {
        }
    }
}

Use this only when the project’s JUnit Jupiter API includes the annotation. Older JUnit 5 projects may not compile with it. Keep Jupiter dependencies on compatible versions, and confirm the version used by the project; the JUnit 5.13.4 release notes identify @SentenceFragment as a 5.13 feature.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When to use a custom generator

If the built-ins cannot express a stable team convention, you can implement DisplayNameGenerator. A custom generator must implement the interface and provide a default constructor. For a modern API, its methods include enclosing-instance-type information:

import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.DisplayNameGenerator;

public final class BusinessDisplayNameGenerator
        implements DisplayNameGenerator {

    public BusinessDisplayNameGenerator() {
    }

    @Override
    public String generateDisplayNameForClass(Class<?> testClass) {
        return humanize(testClass.getSimpleName());
    }

    @Override
    public String generateDisplayNameForNestedClass(
            List<Class<?>> enclosingInstanceTypes,
            Class<?> nestedClass) {
        return humanize(nestedClass.getSimpleName());
    }

    @Override
    public String generateDisplayNameForMethod(
            List<Class<?>> enclosingInstanceTypes,
            Class<?> testClass,
            Method testMethod) {
        return humanize(testMethod.getName());
    }

    private static String humanize(String value) {
        return value.replace('_', ' ');
    }
}

This example humanizes underscores only; it is a starting point, not a universal grammar engine. Custom logic adds maintenance and compatibility costs. Older examples may use deprecated overloads, so check the Javadoc for the JUnit version targeted by your project before copying an implementation. The current API is documented in the versioned interface reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Write names that stay useful

  • Describe behavior. Prefer returns_empty_when_no_matching_users_exist to calls_repository.
  • Name the relevant condition. For example, throws_exception_when_token_is_expired.
  • Keep names stable. A behavior-based name is less likely to become misleading after an implementation change.
  • Keep them concise. preserves_original_order_when_results_are_paginated is more scannable than encoding every status, header, log, and response detail in one name.
  • Use a consistent grammatical style. Names such as works, does_the_thing, and test1 do not tell a reader what passed or failed.

If a method name is getting unwieldy, move context into a nested class, use a concise parameterized invocation name, or give one important case a targeted @DisplayName. Explicit names accept spaces, special characters, and emoji, but terminal output, XML consumers, dashboards, and log parsers may render unusual characters inconsistently. Ordinary text is the safer choice for names consumed by automation.

Troubleshoot a generator that appears not to work

  • Check for @DisplayName. It overrides generated names by design.
  • Check the property file. Confirm it is src/test/resources/junit-platform.properties, the key is exactly junit.jupiter.displayname.generator.default, and the test runtime includes the test resources.
  • Check the generator class name. The property uses org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores; the annotation uses DisplayNameGenerator.ReplaceUnderscores.class.
  • Check the engine. Jupiter annotations and generators apply to JUnit Jupiter tests, not tests running under JUnit Vintage.
  • Separate configuration from rendering. Temporarily annotate a class with @DisplayNameGeneration. If that works, investigate property loading; if it does not, check imports, dependencies, and the test engine.
  • Check parameterized invocation names. Set @ParameterizedTest(name = "...") for individual cases; a display-name generator alone does not set that pattern.

Do not expect identical presentation in every IDE, console launcher, XML report, or CI dashboard. JUnit provides display-name information, while the consuming tool controls how it renders it.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.