Companion code for the IT Talk "JUnit 6 vs TestNG 7".
Previous edition: TestNG vs. JUnit 4 slides · TestNG vs. JUnit 4 webinar
Related projects:
- 🧪 TestNG Workshop — companion TestNG examples to compare side-by-side with this repo
- 🌐 Selenium Example (JUnit 6 branch) — real-world Selenium WebDriver framework built on top of JUnit 6
Hands-on examples for JUnit 6, JUnit 5, and JUnit 4 in Java — covering every major feature from basic assertions to advanced extensions, parameterized tests, retry strategies, parallel execution, and suite orchestration.
Use it as a practical reference, a workshop starter kit, or a side-by-side comparison with the companion TestNG Workshop.
- Found it useful?
- Why this repository?
- Who Is This For?
- Branches
- Quick Start
- Prerequisites
- Supported Versions
- Feature Map
- Learning Path — Beginners
- Advanced Topics — Path for Senior Engineers
- Command Examples
- CI / CD
- AI Assistant Support
- Project Structure
- Additional Resources
- Useful Links
- License
If you found useful examples or information in this repository, please give it a ⭐
Your support helps the project reach more Java and QA engineers.
Most JUnit tutorials stop at @Test and @BeforeEach.
This project goes further — it covers the full JUnit 5 / JUnit 6 feature set with real, runnable code you can clone, execute, and adapt immediately.
Use this repository if you want to:
- learn JUnit from scratch with a structured, step-by-step learning path
- understand the differences between JUnit 4, JUnit 5, and JUnit 6 — what changed and why
- see real runnable examples instead of reading documentation only
- explore advanced features — extensions (
BeforeAllCallback,AfterAllCallback,TestWatcher), retry strategies, parallel execution, tagging, and parameterized tests - study automated testing patterns in Java — soft assertions, data-driven tests, conditional execution, and more
- understand suite orchestration —
@BeforeSuite,@AfterSuite,@Suite, and suite-like lifecycle via custom extensions - compare JUnit and TestNG patterns and idioms side-by-side with the companion TestNG Workshop
- use it as material for workshops, tech talks, onboarding sessions, and self-study
| Audience | What you will get |
|---|---|
| QA engineers new to JUnit 6 | A guided tour of every major feature with runnable examples |
| Java developers migrating from JUnit 4 / TestNG | Side-by-side comparison of patterns and idioms |
| Senior / lead engineers | Deep-dives into extensions, retry strategies, parallel execution, and tagging |
| Workshop facilitators | A ready-made project you can hand to attendees |
This repository contains examples for multiple JUnit versions, each on its own branch:
| Branch | JUnit version | Description |
|---|---|---|
master |
JUnit 6 (6.1.3) | Current branch — latest JUnit 6 features |
junit-5.14.4 |
JUnit 5 (5.14.4) | Stable JUnit 5 examples — the most widely used version |
junit-4.13.2 |
JUnit 4 (4.13.2) | Legacy JUnit 4 examples — useful for migration reference |
ℹ️ Choose Your Build Tool:
This project supports both Maven and Gradle independently. Choose the tool you prefer (or use the included wrappers) and consistently use its corresponding commands:
- Maven: use
./mvnw(Linux/macOS) ormvnw.cmd(Windows)- Gradle: use
./gradlew(Linux/macOS) orgradlew.bat(Windows)
git clone https://github.com/a-oleynik/junit-workshop.git
cd junit-workshop
# Maven
./mvnw clean test
# Gradle
./gradlew clean testThe same checkout can be imported as either build system:
- Maven project: open or import
pom.xml - Gradle project: open or import
settings.gradle/build.gradle
In IntelliJ IDEA, open the repository folder and choose the build model you want when the IDE detects both Maven and Gradle. Do not link both models at the same time: select one build system for the current IDE project.
| Tool | Minimum version | Notes |
|---|---|---|
| JDK | 21 LTS | Required by both build configurations |
| Maven | 3.9+ (optional) | The included Maven Wrapper can be used instead |
| Gradle | 9.6.0 (optional) | The included Gradle Wrapper can be used instead |
| IDE | Any | IntelliJ IDEA is recommended |
| Lombok plugin | Latest | IntelliJ: Settings → Plugins → Lombok |
💡 Maven and Gradle Wrappers included — no local Maven or Gradle installation is required. Each wrapper automatically downloads its pinned build-tool version on first run.
# Linux / macOS (or Windows with Git Bash) ./mvnw clean test ./gradlew clean test # Windows (Command Prompt / PowerShell) mvnw.cmd clean test gradlew.bat clean test
| Maven/Gradle artifact | Version | Purpose |
|---|---|---|
junit-jupiter-engine |
6.1.3 (BOM) |
Test engine — discovers and runs Jupiter tests; transitively provides junit-jupiter-api (all @Test, @BeforeEach, @AfterAll, … annotations) |
junit-jupiter-params |
6.1.3 (BOM) |
@ParameterizedTest, @ValueSource, @CsvSource, @MethodSource, @CsvFileSource |
junit-platform-suite |
6.1.3 (BOM) |
@Suite, @SelectClasses, @BeforeSuite, @AfterSuite |
junit-pioneer |
2.3.0 |
@RetryingTest, @CartesianTest, and other community extensions |
junit-jupiter-params-dataprovider |
2.12 |
TNG-style @DataProvider integration for JUnit Jupiter |
assertj-core |
3.27.7 |
Fluent assertion library; SoftAssertions for collecting multiple failures |
hamcrest-library |
3.0 |
Matcher-based assertions — assertThat(value, matcher) |
lombok |
1.18.48 |
@Builder, @Data — compile-time code generation; reduces boilerplate in model classes |
rerunner-jupiter |
2.1.6 |
@RepeatedIfExceptionsTest — auto-retry flaky tests on failure |
opencsv |
5.12.0 |
CSV file parsing for data-driven tests (CSVParameterizationTest) |
org.gradle.test-retry |
1.6.5 |
Gradle plugin behind the -PretryCount=N command |
Java --release |
21 |
Java language level for compilation — enforces JDK 21 API surface (<release> is stricter than <source>+<target>) |
| Package / folder | Feature demonstrated | Test class(es) |
|---|---|---|
general |
Basic assertions (assertEquals, assertTrue, assertNull, …) |
AssertTest |
general |
Exception testing (assertThrows) |
ExceptionTest |
general |
Test fixtures (@BeforeEach, @AfterEach, @BeforeAll, @AfterAll) |
FixturesTest |
general |
Hamcrest matchers | HamcrestTest |
general |
Timeouts (@Timeout) |
TimeoutTest |
general |
Disabling tests (@Disabled) |
DisabledTest |
general |
Display names & name generators | DisplayNameTest, DisplayNameGenerationTest |
group/asserts |
Grouped / soft assertions (assertAll) |
AssertAllTest |
group/asserts |
AssertJ soft assertions | SoftAssertionsAssertJTest, SoftAssertionsAssertJBDDTest |
group/asserts |
JUnit 5+ soft assert pattern | SoftAssertTest |
conditional |
Assumptions (assumeTrue, assumeThat) |
AssumptionsTest, AssumptionsBeforeAllTest |
ddt |
Parameterized tests — @MethodSource |
ParameterizationTest |
ddt |
Parameterized tests — @ValueSource / @CsvSource |
ValueSourceTest |
ddt |
CSV file data source (src/test/resources/numbers.csv) |
CSVParameterizationTest |
ddt |
TNG DataProvider integration | DataProviderTest |
ddt |
JUnit Pioneer Cartesian product | PioneerCartesianProductTest |
nested |
@Nested test classes |
NestedTest |
grouping |
Tagging with @Tag and custom tag annotations |
TagsTest |
execution/order |
Test execution ordering (@TestMethodOrder) |
ExecutionOrderWithTest |
extensions |
Custom Extension (@RegisterExtension) |
DBResourceExtensionTest |
extensions |
TestWatcher extension |
TestWatcherExtensionTest |
retry |
Retry with JUnit Pioneer (@RetryingTest) |
RetryPioneerTest |
retry |
Retry with Rerunner Jupiter | RetryRerunnerTest |
repeat |
@RepeatedTest |
RetryRepeatedTest |
suites |
Suite lifecycle (@Suite, @BeforeSuite, @AfterSuite, @SelectClasses) |
BeforeAfterSuite, SuiteLifecycleFirstCase, SuiteLifecycleSecondCase |
suites/extension |
Suite-like global lifecycle via BeforeAllCallback + root ExtensionContext store |
SuiteExtensionFirstTest, SuiteExtensionSecondTest |
Work through these topics in order; each builds on the previous one.
-
Basic assertions →
AssertTest
LearnassertEquals,assertTrue,assertNull,assertAll, andfail. -
Test lifecycle →
FixturesTest
Understand@BeforeEach,@AfterEach,@BeforeAll,@AfterAll. -
Exception testing →
ExceptionTest
UseassertThrowsto assert that code throws the right exception. -
Disabling & display names →
DisabledTest,DisplayNameTest,DisplayNameGenerationTest
Skip tests cleanly and make reports human-readable.
DisplayNameGenerationTestdemonstrates class-level name generators with@DisplayNameGeneration. -
Hamcrest matchers →
HamcrestTest
Write expressive assertions withassertThat. -
Grouped assertions →
AssertAllTest
UseassertAllso multiple failures are reported together. -
Assumptions →
AssumptionsTest
Skip tests dynamically when preconditions aren't met. -
Parameterized tests →
ValueSourceTest,ParameterizationTest
Drive one test method with many data rows. -
Nested tests →
NestedTest
Organise related scenarios using inner@Nestedclasses. -
Tagging →
TagsTest
Mark tests asSmokeorRegressionand run subsets from the command line.
Run the whole beginner suite:
# Maven
./mvnw clean test
# Gradle
./gradlew clean testThese topics assume familiarity with JUnit 5+ basics.
TagsTest → tags/ package
Compose @Tag into reusable meta-annotations (@Smoke, @Regression).
CSVParameterizationTest, DataProviderTest, ParameterizationTest
Load test data from CSV files and external ArgumentsProvider / @MethodSource classes.
PioneerCartesianProductTest
Generate all combinations of parameter sets automatically.
SoftAssertionsAssertJTest, SoftAssertionsAssertJBDDTest
Collect all assertion failures before reporting — no early bail-out.
DBResourceExtensionTest, TestWatcherExtensionTest
Implement BeforeAllCallback, AfterAllCallback, and TestWatcher to manage external resources and observe test
outcomes.
ExecutionOrderWithTest
Control method execution order with @TestMethodOrder and @Order.
RetryPioneerTest — @RetryingTest(maxAttempts, minSuccess) via JUnit Pioneer
RetryRerunnerTest — Rerunner Jupiter integration
RetryRepeatedTest — JUnit 5+ native @RepeatedTest
Configured globally in both pom.xml (Surefire) and build.gradle:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.executor-service=worker_thread_poolTests run concurrently by default. Use @ResourceLock or @Execution(SAME_THREAD) to serialise where needed.
// Serialise a single test class back to the main thread:
@Execution(ExecutionMode.SAME_THREAD)
class MyOrderSensitiveTest { ... }
// Declare a shared resource lock (prevents concurrent access):
@ResourceLock("shared-db-connection")
@Test
void test_that_uses_shared_resource() { ... }💡 See
ExecutionOrderWithTestin theexecution/order/package for ordering examples used alongside parallel execution.
Tags used in this project are Smoke and Regression (defined in Tags.java; also available as meta-annotations @Smoke and @Regression).
Direct tag filtering (works out of the box):
# Run only Smoke tests
./mvnw clean test -Dgroups=Smoke
./gradlew clean test -DincludeTags=Smoke
# Run only Regression tests
./mvnw clean test -Dgroups=Regression
./gradlew clean test -DincludeTags=Regression
# Run both tags
./mvnw clean test -Dgroups=Smoke,Regression
./gradlew clean test -DincludeTags=Smoke,RegressionGradle also supports -DexcludeTags=TagName; both properties may be supplied together.
Via Maven profiles or dedicated Gradle tasks:
./mvnw clean test -P Smoke
./gradlew clean smokeTest
./mvnw clean test -P Regression
./gradlew clean regressionTestEach Maven profile or Gradle task activates the matching tag filter and automatically excludes
@Suiteclasses, which have no tagged members and would otherwise fail withNoTestsDiscoveredException. Profile IDs intentionally match the@Tagvalues —SmokeandRegression— so the naming is consistent throughout the project.
BeforeAfterSuite → suites/ package
Group multiple test classes under a single @Suite class.
Use @BeforeSuite and @AfterSuite to run setup/teardown logic that wraps the entire suite — not just a single
test class.
@Suite
@SelectClasses({
SuiteLifecycleFirstCase.class,
SuiteLifecycleSecondCase.class
})
public class BeforeAfterSuite {
@BeforeSuite
static void beforeSuite() { ... }
@AfterSuite
static void afterSuite() { ... }
}Execution lifecycle:
[BeforeSuite] @BeforeSuite
[BeforeAll] SuiteLifecycleFirstCase
first_test_in_first_case
second_test_in_first_case
[AfterAll] SuiteLifecycleFirstCase
[BeforeAll] SuiteLifecycleSecondCase
first_test_in_second_case
second_test_in_second_case
[AfterAll] SuiteLifecycleSecondCase
[AfterSuite] @AfterSuite
⚠️ Naming convention: classes selected by a suite must not be named*Testor*Tests.
Use*Caseor*Scenarioinstead.
If they matched Surefire’s or Gradle’s default discovery patterns they would execute twice —
once directly by Surefire / Gradle and once again through the suite.
⚙️ Build config:
pom.xmlandbuild.gradleboth declare the JUnit Platform Suite dependency and include*Suiteclasses in test discovery. In Maven that is expressed through Surefire<includes>; in Gradle it is configured on everyTesttask.
- The
junit-platform-suitedependency (enables@Suite,@BeforeSuite,@AfterSuite):<dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-suite</artifactId> <!-- version managed by junit-bom in dependencyManagement --> </dependency>**/*Suite.javaadded to Surefire<includes>soBeforeAfterSuiteis automatically discovered by./mvnw clean test; the equivalent Gradle include is**/*Suite.class.
SuiteLikeLifecycleExtension → extensions/ package (main); tests → suites/extension/
An alternative to @Suite that provides global setup/teardown without grouping tests under a suite class.
How it works:
- Implement
BeforeAllCallback - Access the root
ExtensionContext— shared across the entire JVM test run computeIfAbsentensures the factory runs only once, on the first class whosebeforeAllfires- Return an
AutoCloseable— JUnit callsclose()when the root context tears down (end of run = “AfterSuite”)
public class SuiteLikeLifecycleExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
context.getRoot()
.getStore(NAMESPACE)
.computeIfAbsent("suite-like-resource", key -> {
System.out.println("Before entire run"); // runs only once
return new SuiteCleanupResource();
}, SuiteCleanupResource.class);
}
static class SuiteCleanupResource implements AutoCloseable {
@Override
public void close() {
System.out.println("After entire run"); // called once at end of all tests
}
}
}Apply @ExtendWith to every test class that participates in the shared lifecycle:
@ExtendWith(SuiteLikeLifecycleExtension.class)
public class SuiteExtensionFirstTest {
@Test
void first() { ...}
}Comparing the two suite-lifecycle approaches:
@Suite + @BeforeSuite |
Extension approach | |
|---|---|---|
| Test class naming | *Case / *Scenario (not *Test) |
*Test — normal, runs independently |
| Requires suite class | ✅ @SelectClasses required |
❌ No suite class needed |
| Tests run independently | ❌ Only via suite entry class | ✅ Normal Surefire / Gradle discovery |
| Opt-in mechanism | Declared in @SelectClasses |
@ExtendWith per class |
ℹ️
computeIfAbsentvsgetOrComputeIfAbsent: In JUnit 6, allgetOrComputeIfAbsentoverloads are deprecated and replaced by identically-signaturedcomputeIfAbsentmethods — this example usescomputeIfAbsent(K, Function, Class<V>). On the JUnit 5 branch, usegetOrComputeIfAbsent(K, Function, Class<V>)instead (same signature, different name).
# Maven
./mvnw clean site
# Gradle
./gradlew clean testMaven writes the report to target/site/surefire-report.html; Gradle writes it to
build/reports/tests/test/index.html automatically as part of the standard test task.
💡 Commands below are provided in pairs for Maven and Gradle. Select the commands corresponding to your chosen build system.
./mvnw clean test
./gradlew clean test./mvnw clean test -Dtest=AssertTest
./gradlew clean test --tests "*AssertTest"./mvnw clean test -Dtest=AssertTest#assert_equals_multiplication_test
./gradlew clean test --tests "*AssertTest.assert_equals_multiplication_test"./mvnw clean test -Dtest=AssertTest,HamcrestTest
./gradlew clean test --tests "*AssertTest" --tests "*HamcrestTest"./mvnw clean test -Dtest=AssertTest#assert_equals*
./gradlew clean test --tests "*AssertTest.assert_equals*"./mvnw clean test -Dtest=AssertTest#assert_equals*+assert_boolean*
./gradlew clean test --tests "*AssertTest.assert_equals*" --tests "*AssertTest.assert_boolean*"./mvnw clean test -Dsurefire.rerunFailingTestsCount=2
./gradlew clean test -PretryCount=2./mvnw clean test -Dgroups=Regression,Smoke
./gradlew clean test -DincludeTags=Regression,Smoke./mvnw clean test -P Smoke
./gradlew clean smokeTest
./mvnw clean test -P Regression
./gradlew clean regressionTest./mvnw test "-Dtest=BeforeAfterSuite"
./gradlew test --tests "*BeforeAfterSuite"
⚠️ Naming convention: suite-member classes (e.g.SuiteLifecycleFirstCase) are named*Case, not*Testor*Tests.
This prevents Surefire and Gradle from discovering them as standalone tests and running them twice —
once directly by Surefire / Gradle and once again through the suite.
./mvnw clean package
./gradlew clean build./mvnw clean package -DskipTests
./gradlew clean assemble./mvnw clean surefire-report:report
./gradlew clean testMaven report:
target/reports/surefire.html— this is the standalone goal (mojo) execution, which uses its own default output directory.
Gradle report:build/reports/tests/test/index.html
./mvnw clean site
./gradlew clean testMaven report:
target/site/surefire-report.html— running the Surefire Report Plugin as part ofmvn siteplaces it alongside the rest of the generated site pages, under a different path than the standalonesurefire-report:reportgoal above.
Gradle report:build/reports/tests/test/index.html
./mvnw clean test -X
./gradlew clean test --debugThis project uses GitHub Actions with a manually triggered workflow.
Triggered manually from Actions → Run workflow on GitHub.
| Input | Required | Description |
|---|---|---|
groups |
No | Tag filter for the by-tag job (e.g. Smoke, Regression). Leave empty to skip that job. |
| Job | Name | Runs when | Maven command | Equivalent Gradle command |
|---|---|---|---|---|
regression |
Regression — all tests | Always | ./mvnw -B clean site |
./gradlew --no-daemon clean test |
by-tag |
By tag — {groups} |
Only when groups input is filled in |
./mvnw -B clean site -Dgroups={groups} |
./gradlew --no-daemon clean test -DincludeTags={groups} |
The current GitHub Actions workflow executes the Maven commands; the Gradle column shows the equivalent commands for local use or for adding a Gradle-based workflow later.
Each job uploads two artifacts after completion — including on failure (if: always()):
| Artifact | Source path | Contents |
|---|---|---|
surefire-report / surefire-report-{tag} |
target/site/ |
Full HTML report — open surefire-report.html in a browser |
junit-xml-results / junit-xml-results-{tag} |
target/surefire-reports/ |
Raw JUnit XML files — compatible with CI dashboards and report parsers |
- Go to the Actions tab on GitHub
- Select Java CI with Maven in the left panel
- Click Run workflow
- Optionally fill in the
groupsfield (e.g.Smoke) to also run the by-tag job - Click Run workflow
- Click the completed workflow run
- Scroll to Artifacts → download
surefire-report(HTML) orjunit-xml-results(XML) - Open
surefire-report.htmlin a browser
# All tests
./mvnw clean site
./gradlew clean test
# By tag
./mvnw clean site -Dgroups=Smoke
./gradlew clean test -DincludeTags=Smoke
./mvnw clean site -Dgroups=Regression
./gradlew clean test -DincludeTags=RegressionThis project ships instruction files for AI coding assistants so they automatically follow the project's conventions when generating or editing code.
| File | Tool | Purpose |
|---|---|---|
AGENTS.md |
Any AI Agent | Task recipes and conventions for AI coding agents — add a test class, extension, data provider, tag, etc. |
.github/copilot-instructions.md |
GitHub Copilot | Always-on workspace instructions — injected into every Copilot suggestion and chat turn |
.junie/guidelines.md |
JetBrains Junie | Project guidelines Junie reads before generating code |
All files encode the same critical conventions:
- naming rules (
*Case/*Scenariofor suite members, never*Test) - assertion style (AssertJ first, Hamcrest only in
HamcrestTest) - extension placement (
src/main/…/extensions/, not test sources) - JUnit 6 API preferences (
computeIfAbsent, not the deprecatedgetOrComputeIfAbsent) - parallel execution awareness (
@Execution(SAME_THREAD)/@ResourceLockwhere needed)
AGENTS.md # AI Agent task recipes and conventions
README.md # This file
LICENSE
pom.xml # Maven build — dependencies, Surefire config, profiles
mvnw / mvnw.cmd # Maven Wrapper scripts (no local Maven installation required)
build.gradle # Gradle build — equivalent dependencies and test configuration
settings.gradle # Gradle project name
gradlew / gradlew.bat # Gradle Wrapper scripts (no local Gradle installation required)
gradle/wrapper/ # Pinned Gradle distribution and wrapper bootstrap JAR
.github/
└── copilot-instructions.md # GitHub Copilot workspace instructions (always-on)
.junie/
└── guidelines.md # JetBrains Junie project guidelines
src/
├── main/java/com/oleynik/qa/workshop/junit/
│ ├── annotations/ # Custom annotation types and helpers
│ ├── dataproviders/ # ArgumentsProvider / DataProvider implementations
│ ├── extensions/ # Extension implementations (SuiteLikeLifecycleExtension, DBResourceExtension, TestWatcherExtension)
│ ├── model/ # Domain model (User, MyDoubleWrapper, MyServer)
│ └── Utils.java
└── test/
├── java/com/oleynik/qa/workshop/junit/
│ ├── general/ # Core assertions, fixtures, exceptions, display names
│ ├── group/asserts/ # Grouped / soft assertions
│ ├── conditional/ # Assumptions
│ ├── ddt/ # Parameterized & data-driven tests
│ ├── nested/ # @Nested test classes
│ ├── grouping/ # @Tag / custom tag annotations
│ │ └── tags/ # @Smoke, @Regression meta-annotations and Tags constants
│ ├── execution/order/ # Test execution ordering
│ ├── extensions/ # Custom JUnit extensions
│ ├── retry/ # Retry strategies (Pioneer, Rerunner)
│ ├── repeat/ # @RepeatedTest
│ └── suites/ # Suite lifecycle (@BeforeSuite, @AfterSuite)
│ └── extension/ # Suite-like global lifecycle via BeforeAllCallback
└── resources/
└── numbers.csv # Input data for CSVParameterizationTest (number → expected factorial)
target/ # Generated by Maven — not committed to git
├── surefire-reports/ # Raw JUnit XML results (TEST-*.xml) + console output (*.txt)
└── site/
└── surefire-report.html # HTML test report — open in a browser after `mvn site`
build/ # Generated by Gradle — not committed to git
├── test-results/test/ # Raw JUnit XML results
└── reports/tests/test/
└── index.html # Gradle HTML test report
- JUnit 6 User Guide
- JUnit Pioneer Documentation
- AssertJ Documentation
- Hamcrest Tutorial
- Lombok Features
- Maven Surefire Plugin
- Maven Surefire Report Plugin
- Maven Wrapper — run Maven without a local installation
- Gradle Java Plugin
- Gradle Wrapper — run Gradle without a local installation
- Gradle Test Retry Plugin
- TestNG Workshop — companion TestNG examples
- Selenium Example — JUnit 6 branch — real-world Selenium framework using JUnit 6
- Java Download
- Maven Download
- Gradle Releases
- JUnit 5 & 6 Releases
- JUnit 4 Releases
- JUnit 6 Released — Clean-Up, Modernization & Minimal Disruption
- What Is a Test Fixture in JUnit, and Why Is @BeforeEach Not a Fixture?
- Maven Wrapper: Why You Need It and How to Use It
- JUnit Pioneer Releases
- Lombok Download
- IntelliJ Lombok Plugin
This project is licensed under the MIT License — see the LICENSE file for details.