QA How-To
Java for Testers: TestNG groups and parallel (2026)
Apply java testers TestNG groups and parallel execution with clear suite taxonomy, XML filters, thread-safe fixtures, isolated data, and reliable CI controls.
22 min read | 2,691 words
TL;DR
Java testers TestNG groups and parallel execution work best as two separate decisions: groups define which tests belong in a run, while the suite's parallel mode defines the ownership boundary for worker threads. Start with a small group vocabulary, use XML includes and excludes, choose the safest parallel mode your fixtures support, and isolate every mutable resource before increasing thread count.
Key Takeaways
- Use groups as a small stable taxonomy for purpose, layer, capability, or operating constraint, not as arbitrary tags.
- Select included and excluded groups in `testng.xml` or build commands so the same compiled tests can serve different pipelines.
- Choose `parallel=tests`, `classes`, `methods`, or `instances` according to the narrowest state-sharing boundary your code can safely support.
- Make configuration teardown `alwaysRun = true` so filtering, dependencies, and failures do not leak resources.
- Give every concurrent invocation its own driver, account, test records, download path, and report artifact names.
- Treat DataProvider parallelism and suite parallelism as related but separately controlled thread pools.
- Increase thread counts only after measuring Grid, application, database, and CI agent capacity.
Java testers TestNG groups and parallel execution solve different problems that are often configured together. Groups select the right tests for smoke, regression, API, UI, or another governed purpose. Parallel settings decide which TestNG units may run on different threads. A good suite makes both choices explicit and keeps state isolated at the chosen boundary.
This guide shows current TestNG annotations and XML for group selection, meta-groups, dependencies, and the four core parallel modes. It then connects those settings to Selenium drivers, DataProviders, test data, cleanup, CI capacity, and evidence so faster execution does not become flaky execution.
TL;DR
| Goal | TestNG mechanism | Safe starting point |
|---|---|---|
| Run a fast release gate | Include smoke |
Keep membership reviewed and small |
| Omit known destructive cases | Exclude destructive |
Also protect the target environment |
Run independent <test> blocks concurrently |
parallel="tests" |
Good for non-thread-safe legacy classes grouped by test tag |
| Run classes concurrently | parallel="classes" |
Good when fields are class-confined |
| Run methods concurrently | parallel="methods" |
Requires invocation-safe fixtures and no mutable instance sharing |
| Run factory instances concurrently | parallel="instances" |
Keeps methods on one instance together |
| Run DataProvider rows concurrently | @DataProvider(parallel = true) |
Isolate every row and size its pool |
Do not choose methods merely because it sounds fastest. Choose the most concurrent mode that preserves the suite's real state boundaries, then verify it under repetition.
1. Java Testers TestNG Groups and Parallel: Two Independent Axes
A group is metadata on a test method or class. TestNG can include or exclude that metadata without recompiling. Parallel execution is scheduling policy. The same smoke group can run sequentially on a developer laptop and with four class workers in CI. Keeping these axes independent produces reusable test code.
A method can belong to several groups:
package example.groups;
import org.testng.annotations.Test;
public class CheckoutTest {
@Test(groups = {"smoke", "ui", "checkout"})
public void cardPaymentCompletes() {
// Exercise one checkout contract.
}
@Test(groups = {"regression", "ui", "checkout"})
public void expiredCardIsRejected() {
// Exercise one validation contract.
}
}
Group membership should describe a durable reason for selection. smoke describes execution purpose, ui describes layer, and checkout describes capability. A temporary label such as run-this-week has no stable ownership and will rot.
Parallel safety is not inherited from group membership. A smoke method can still share a mutable account, and an api test can still corrupt a static client. Before selecting a mode, inventory state at suite, <test>, class, instance, method, and DataProvider-row scope. The mode must not place code that shares unsafe state onto different threads.
2. Create a Small, Governed Group Taxonomy
Group sprawl makes selection unpredictable. Use a handful of dimensions and publish their definitions. A practical taxonomy might include purpose (smoke, regression), layer (api, ui, contract), capability (checkout, accounts), and constraint (destructive, serial, requires-grid). Not every test needs a label from every dimension.
Use constants to avoid spelling drift while retaining compile-time annotation values:
package example.groups;
public final class TestGroups {
public static final String SMOKE = "smoke";
public static final String REGRESSION = "regression";
public static final String API = "api";
public static final String UI = "ui";
public static final String SERIAL = "serial";
public static final String DESTRUCTIVE = "destructive";
private TestGroups() {
}
}
@Test(groups = {TestGroups.SMOKE, TestGroups.API})
public void healthEndpointReturnsReady() {
// API assertion.
}
| Group type | Good examples | Avoid |
|---|---|---|
| Purpose | smoke, regression |
important, misc |
| Layer | api, ui, contract |
Tool names that may change without behavior changing |
| Capability | checkout, search, accounts |
Individual class names |
| Constraint | serial, destructive |
Permanent flaky with no defect owner |
| Environment need | requires-grid, requires-sandbox |
works-on-my-machine |
Review groups in code review. If smoke grows until it takes an hour, it no longer serves a rapid release signal. If a test is quarantined, use an owned issue, expiry, and explicit pipeline rule rather than an undocumented permanent group.
3. Select and Combine Groups in testng.xml
A suite file can include and exclude groups under <groups><run>. Exclusion is useful for destructive or unsupported cases, but environment authorization must still be enforced in setup.
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Release gate">
<test name="Smoke without destructive cases">
<groups>
<run>
<include name="smoke"/>
<exclude name="destructive"/>
</run>
</groups>
<packages>
<package name="example.tests"/>
</packages>
</test>
</suite>
TestNG supports regular expressions in group selection. Use them carefully and remember they are regex patterns, not shell wildcards. Exact names are easier to audit. A broad expression can silently include a new group later.
Meta-groups let XML define a named combination without adding another annotation everywhere:
<groups>
<define name="release">
<include name="smoke"/>
<include name="contract"/>
</define>
<run>
<include name="release"/>
<exclude name="destructive"/>
</run>
</groups>
Keep meta-groups close to pipeline configuration and document the final resolved membership. When a build unexpectedly runs zero tests, print selected groups and verify discovered invocation counts. A green job with no executed tests is a pipeline defect, not success.
Build tools can override groups. Maven Surefire supports commands such as mvn test -Dgroups=smoke -DexcludedGroups=destructive. Standardize one entry point so local and CI selection do not drift.
4. Apply Groups at Method and Class Level
Annotating a class with @Test(groups = ...) adds that group to its test methods. A method can add more groups. Class-level membership is useful when every behavior in a feature shares a stable layer or capability.
package example.groups;
import org.testng.annotations.Test;
@Test(groups = {TestGroups.API, "accounts"})
public class AccountApiTest {
@Test(groups = TestGroups.SMOKE)
public void createsAccount() {
// Belongs to api, accounts, and smoke.
}
@Test(groups = TestGroups.REGRESSION)
public void rejectsDuplicateEmail() {
// Belongs to api, accounts, and regression.
}
}
Do not annotate a large base class with smoke merely because some subclasses should be smoke tests. Inherited or class-level membership can widen selection in ways reviewers miss. Put purpose groups close to the behavior they classify. Layer or capability groups are more natural at class level.
Configuration methods can also be restricted by groups, but lifecycle behavior becomes subtle when methods belong to multiple groups and selection changes. Prefer simple method-scoped setup that can inspect the selected test's needs, or split tests into coherent classes with matching fixtures.
Always ask whether a group controls selection or behavior. It should normally control selection. A test that changes its assertions when smoke is active is two tests hidden in one method. Keep the assertion contract fixed and use separate tests or explicit configuration for true environment variation.
5. Make Configuration Methods Safe With Group Filtering
TestNG configuration annotations support group-aware execution, and @BeforeGroups or @AfterGroups can run around the first and last method of named groups. They are useful for narrow group resources but can surprise teams when parallel scheduling and overlapping groups are involved.
For per-test resources, prefer always-run method cleanup:
package example.groups;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
public abstract class IsolatedTestBase {
@BeforeMethod(alwaysRun = true)
public void createInvocationResources() {
// Create only resources owned by this invocation.
}
@AfterMethod(alwaysRun = true)
public void releaseInvocationResources() {
// Capture evidence, then release resources in finally blocks.
}
}
alwaysRun = true helps teardown execute even when the test fails or a dependency is skipped. It does not rescue a JVM killed by the operating system, so external resources should also have TTLs or cleanup jobs.
Use @BeforeGroups("database") only when one group truly owns a shared fixture and the fixture is thread-safe. If group methods run in parallel and mutate that fixture, first and last boundaries do not serialize the individual methods. A connection pool can be shared; a single transaction or mutable record usually cannot.
When filters change, run a small lifecycle verification suite that records configuration and test events. This catches resources created for excluded tests, cleanup that never runs, and incorrect assumptions about group inheritance.
6. Use Dependencies Without Building One Long Scenario
TestNG supports dependsOnMethods and dependsOnGroups. If a prerequisite fails, dependent tests are skipped unless configuration says otherwise. Dependencies are useful when one check has no meaning without a prerequisite, but they are often misused to create ordered end-to-end workflows.
@Test(groups = "authentication")
public void authenticationServiceIsAvailable() {
// Assert a narrow prerequisite.
}
@Test(
groups = "checkout",
dependsOnGroups = "authentication"
)
public void authenticatedUserCanCheckout() {
// Use independent setup and assert checkout.
}
The dependent test should not rely on the first test's browser, access token, or created user. A dependency controls scheduling and skip behavior, not data transfer. Create required state through fixtures or APIs. Otherwise a test passes only when executed after another method and cannot be retried or sharded independently.
Dependencies also constrain parallel schedules. TestNG respects the declared order, reducing available concurrency. A web of group dependencies becomes difficult to predict and can cause many skips from one failure. Prefer a setup assertion when every test requires the same service, and reserve dependencies for meaningful conditional execution.
Do not use priority as a substitute for ownership. Priority influences scheduling but does not create a robust business workflow contract. Independent tests should remain valid in any order.
7. Choose the Correct TestNG Parallel Mode
TestNG suite XML supports methods, tests, classes, and instances. Each mode defines which units may be assigned to different threads.
| Mode | Same-thread guarantee | Good fit | Main risk |
|---|---|---|---|
tests |
Methods inside one <test> tag |
Legacy classes that share state within a test block | Large blocks limit concurrency |
classes |
Methods in one class | Class-owned immutable or safely reset fixtures | Mutable static state still crosses classes |
methods |
No class-level same-thread assumption | Fully invocation-isolated tests | Instance fields and shared fixtures race |
instances |
Methods on one factory instance | Factory-created scenario objects | Static resources still collide |
A class-parallel suite:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Regression" parallel="classes" thread-count="4">
<test name="All tests">
<packages>
<package name="example.tests"/>
</packages>
</test>
</suite>
Start with classes if each class manages its own driver and fields but individual methods within a class are not ready to overlap. Move to methods only after eliminating or isolating mutable instance fields. Use tests when each XML <test> represents a browser, region, or other block that must stay on one thread.
thread-count is a maximum scheduling control, not a promised speedup. Dependencies, limited test count, locks, DataProvider pools, and application response time influence actual concurrency. Measure wall time and failure quality together.
8. Isolate Drivers, Data, Clients, and Artifacts
Parallel scheduling exposes hidden shared state. Inventory every mutable resource and assign an owner. One TestNG worker or invocation should own one Selenium session, test user or tenant, created records, download directory, and artifact namespace. HTTP clients can be shared only when their implementation and attached state, such as cookies or mutable headers, are safe.
For Selenium, use a method-scoped driver stored explicitly or in ThreadLocal<WebDriver>. Never share one static session. The Singleton driver factory guide includes a complete start, get, stop lifecycle.
Generate unique data with a run ID plus case ID, not just the thread number. TestNG can reuse a thread for several methods, and reruns can execute on a different thread. Clean data in finally blocks and give remote fixtures a time-to-live as a second defense.
Avoid these shared mutables:
private static WebDriver driver;
private static String currentOrderId;
private static final String DOWNLOAD = "target/downloads/result.csv";
Prefer invocation-local variables and unique paths:
String caseId = "checkout-" + java.util.UUID.randomUUID();
java.nio.file.Path downloadDir = java.nio.file.Files.createTempDirectory(caseId);
Reports also need concurrency safety. Use listener implementations backed by thread-safe collections or framework-supported nodes, and key artifacts by stable invocation ID. A single mutable "current test" field in a listener will attach screenshots to the wrong result.
9. Java Testers TestNG Groups and Parallel Thread Pools
@DataProvider(parallel = true) schedules parameter rows through DataProvider parallelism. This can operate alongside suite method or class parallelism. Treat their capacity as related even when TestNG uses separate pools.
Current TestNG suite settings include data-provider-thread-count. With the 1.1 DTD, share-thread-pool-for-data-providers="true" shares a pool among parallel providers, and use-global-thread-pool="true" can use the suite's global pool for regular and data-driven tests. Choose one model deliberately rather than accepting accidental concurrency.
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.1.dtd">
<suite name="Parallel API"
parallel="methods"
thread-count="8"
data-provider-thread-count="8"
use-global-thread-pool="true">
<test name="Contract and regression">
<groups>
<run><include name="api"/></run>
</groups>
<packages><package name="example.api"/></packages>
</test>
</suite>
A global pool makes the maximum easier to reason about, but it does not fix shared data. Each DataProvider row still needs a unique case identity and resources. The TestNG DataProvider guide covers typed rows and provider validation.
Do not set thread counts only from CPU cores. UI tests consume browser and Grid capacity; API tests consume connection pools, rate limits, and downstream services. Run controlled steps such as 2, 4, and 8 workers, then compare throughput, p95 test duration, infrastructure errors, and retry rate. The values are an experimental sequence, not a universal recommendation.
10. Build a Reliable CI Execution Matrix
Map each pipeline to a clear selection and parallel policy. A pull request might run smoke without destructive tests using class parallelism. A nightly job might run regression across browsers with separate <test> blocks. A serial lane can include tests labeled serial and run with no parallel attribute.
Keep suite files small and versioned. Print these facts at job start:
- Included and excluded groups.
- Suite file and TestNG version from dependency resolution.
- Parallel mode and thread-pool settings.
- Browser or API target without credentials.
- Shard, run ID, and environment safety mode.
- Discovered and completed invocation counts.
Run parallel stability checks before increasing the default. Repeat the selected suite, randomize order where appropriate, and compare with a sequential baseline. A failure that disappears sequentially is evidence of shared state or capacity, not proof that parallel TestNG is unreliable.
Track duration, first-attempt failure rate, infrastructure errors, and leaked resources by worker count. If throughput stops improving, reduce workers or increase target capacity. Do not use retry analyzers to absorb concurrency defects. The TestNG retry analyzer guide explains how retry passes should remain visible.
Finally, enforce a nonzero test count and expected group coverage. A typo in smkoe can otherwise create the fastest and least useful green pipeline.
11. Verify the Scheduling Harness
Add a canary suite that intentionally exposes the scheduling boundary. Two classes can record their thread IDs to confirm class parallelism, while two methods in one class confirm they remain together. A method-parallel variant can use barriers around invocation-local work to prove overlap without depending on timing sleeps. Keep this canary separate from product assertions so a TestNG upgrade or suite-file change gives a precise framework failure.
Repeat the canary with group filters. Confirm included configuration and tests execute, excluded groups do not allocate browsers, and teardown still runs after a controlled assertion failure. Then inspect the report for unique invocation IDs and correct artifact ownership. These tests protect the execution harness itself, which ordinary green product tests may not exercise fully.
When sharding is added above TestNG, calculate total concurrency across shards, suite workers, and provider workers. Four CI shards with eight workers each can create 32 simultaneous sessions before separate provider pools are considered. Publish that maximum beside the Grid and environment limit. Scaling the orchestration layer without revisiting the suite XML is a common way to turn a stable parallel design into capacity failures.
Interview Questions and Answers
Q: What are TestNG groups used for?
Groups classify tests so suites and build commands can include or exclude them without recompiling. Good groups describe stable execution purpose, layer, capability, or constraint.
Q: What is the difference between parallel=tests and parallel=classes?
With tests, methods inside the same XML <test> tag stay on the same thread while different tags may run concurrently. With classes, methods in one class stay together while different classes may run on different threads.
Q: When is parallel=methods safe?
It is safe when each method invocation owns its mutable resources and does not depend on shared instance fields, drivers, accounts, records, or filenames. Configuration and reporting must also be thread-safe.
Q: Can a method belong to multiple TestNG groups?
Yes. A method can be smoke, UI, and checkout at the same time. Selection can include or exclude those group names according to suite policy.
Q: How do group dependencies affect parallel execution?
TestNG delays dependent methods until prerequisite methods or groups complete. The dependency limits concurrency and can propagate skips, but it should not transfer mutable data between tests.
Q: Is ThreadLocal enough to make a Selenium suite parallel-safe?
No. It can isolate drivers by worker, but accounts, database records, files, static fields, listeners, and target capacity also need safe ownership.
Q: How do DataProvider threads interact with suite threads?
Parallel DataProviders can use their own pool or current TestNG options can share or unify pools. Configure the combined concurrency deliberately and keep each parameter row isolated.
Common Mistakes
- Creating dozens of undocumented, overlapping group names.
- Using a group to change assertion behavior instead of selecting tests.
- Assuming
smoketests are automatically thread-safe. - Choosing
parallel=methodswhile test classes hold mutable instance fields. - Sharing a static WebDriver, account, or order ID.
- Using
priorityto create a long dependent business workflow. - Passing data through one test to a dependent test.
- Forgetting
alwaysRun = trueon critical teardown. - Ignoring the separate DataProvider concurrency settings.
- Setting thread count above Grid or application capacity.
- Letting parallel workers overwrite one screenshot or download.
- Accepting a green run that discovered zero selected tests.
Conclusion
Java testers TestNG groups and parallel execution become reliable when selection and scheduling remain separate, explicit policies. Use a small group taxonomy, versioned include and exclude rules, and the parallel mode that matches your true state boundary. Then isolate browsers, accounts, records, clients, and artifacts before adding workers.
Start with a sequential baseline and class-level parallelism, measure capacity, and move to method or DataProvider concurrency only when evidence supports it. A fast suite is useful only when every selected test still has deterministic setup, trustworthy cleanup, and a result that another engineer can reproduce.
Interview Questions and Answers
How would you organize TestNG groups in a large framework?
I would define a small documented vocabulary across purpose, layer, capability, and constraints. Constants would prevent spelling drift, code review would govern membership, and pipeline suite files would own combinations.
Explain the four TestNG suite parallel modes.
`tests` separates XML test tags, `classes` separates classes, `methods` can separate methods, and `instances` separates factory-created instances. Each mode keeps the named lower-level boundary together as documented, so I choose based on mutable state ownership.
What would you check before enabling parallel methods?
I would inspect driver scope, instance and static fields, test accounts, record creation, files, clients, listeners, dependencies, and teardown. I would also confirm Grid and application capacity with a controlled worker ramp.
How should Selenium drivers be managed in parallel TestNG tests?
Each invocation or worker needs an independent WebDriver, created before the method and quit in always-run teardown. A thread-local manager can work when TestNG lifecycle stays on the worker thread.
What is a TestNG meta-group?
It is an XML-defined group that includes other groups. It lets a pipeline name a combination such as release without adding another annotation to every test method.
Why are dependent tests risky in a parallel suite?
Dependencies reduce scheduling freedom and can propagate skips. They become especially fragile when one test transfers mutable state to another, so fixtures should create independent prerequisites instead.
How would you validate that parallelization improved the suite?
I would compare wall time, individual duration distribution, first-attempt failure rate, infrastructure errors, and leaked resources at several worker counts. I would keep a sequential baseline to expose concurrency-specific defects.
Frequently Asked Questions
How do I run a specific group in TestNG?
Include the group under `<groups><run>` in `testng.xml` or pass it through your build tool, such as Maven Surefire's `-Dgroups=smoke`. Verify that the run discovers a nonzero number of tests.
What parallel modes does TestNG support?
The core suite modes are `tests`, `classes`, `methods`, and `instances`. They differ in which methods or objects TestNG keeps on the same thread.
Which TestNG parallel mode should I use first?
Start with `classes` when fields and drivers are class-confined, or `tests` for legacy blocks that must remain on one thread. Use `methods` only after every invocation is isolated.
How do I exclude a TestNG group?
Add `<exclude name="destructive"/>` inside the suite's group run block or use your build tool's excluded-groups option. Environment-side authorization should still block unsafe actions.
Can TestNG groups use regular expressions?
Yes, group include and exclude patterns support regular expressions. Prefer exact names where possible because broad patterns can silently change membership when new groups are added.
Why do TestNG tests fail only in parallel?
Common causes are shared drivers, accounts, records, static fields, output files, non-thread-safe listeners, or target-system saturation. Compare with a sequential run and correlate failures with shared resources and worker count.
What does data-provider-thread-count control?
It controls the maximum provider worker pool size for parallel DataProvider invocations under the applicable TestNG pool configuration. It should be sized with the suite thread policy and target capacity.