In REST Assured, how do RestAssuredConfig.config() and RestAssured.config() differ when you add a setting?
answer
- same name, different declaring class
- one builds fresh, one reads current
- setters copy, never mutate
- assign the result or nothing happens
basics
~20 sRestAssuredConfig.config() is a factory returning a brand-new, all-default config; RestAssured.config() returns whatever is currently assigned to the static field. Building from the factory silently discards earlier settings, because each setter copies the other seventeen objects forward unchanged.
solid answer
~50 sThey share a name and nothing else. `RestAssuredConfig.config()` is a static factory on the config class: it returns `new RestAssuredConfig()` every time, with all eighteen config objects at their defaults, and `newConfig()` is the very same method under a second name. `RestAssured.config()` is an accessor on the entry-point class that hands back whatever currently sits in the public static `RestAssured.config` field. Because `RestAssuredConfig` is immutable, a setter such as `httpClient(...)` mutates nothing; it builds a new `RestAssuredConfig` carrying the other seventeen objects forward. So `RestAssured.config = config().httpClient(...)` starts from defaults and throws away anything set earlier, while `RestAssured.config = RestAssured.config().httpClient(...)` accumulates. Both are usually static-imported, which is exactly why they get confused: a bare `config()` in a documentation snippet is the factory. Once more than one setup method in a marina suite touches configuration, the accumulating form is the only safe one.
code
java · 20 linesimport static io.restassured.config.RestAssuredConfig.config;
import static io.restassured.config.DecoderConfig.decoderConfig;
import static io.restassured.config.DecoderConfig.ContentDecoder.DEFLATE;
import static io.restassured.config.HttpClientConfig.httpClientConfig;
import static io.restassured.config.RedirectConfig.redirectConfig;
// base setup for the marina suite
RestAssured.config = config()
.decoderConfig(decoderConfig().contentDecoders(DEFLATE));
// WRONG: RestAssuredConfig.config() is a factory, so the decoder above is gone
RestAssured.config = config()
.httpClient(httpClientConfig().reuseHttpClientInstance());
// RIGHT: RestAssured.config() hands back what is currently assigned
RestAssured.config = RestAssured.config()
.httpClient(httpClientConfig().reuseHttpClientInstance());
// and this line changes nothing at all - the new instance is discarded
RestAssured.config().redirect(redirectConfig().maxRedirects(2));go deeper
Learn the two spellings and what each returns. When you copy a configuration line from documentation, check whether the bare config() in it is the factory or the accessor before pasting it into shared setup.
Explain the immutability: each setter returns a new RestAssuredConfig with the other seventeen objects copied forward, which is why an unassigned chain is a no-op and why the factory form resets the suite.
Diagnose the ordering-dependent failure this causes, where the symptom lands in a test that never mentions configuration and only appears when two setup methods run together.
Decide who is allowed to assign RestAssured.config at all. Argue for a single owning hook over per-class setup, and for making configuration failures loud rather than silently defaulted.
## Two methods, one name, different declaring types REST Assured has two public methods spelled `config()`, and the difference between them is the difference between keeping your settings and losing them. - `io.restassured.config.RestAssuredConfig.config()` is a **static factory**. Its body is `return new RestAssuredConfig();`. Every call produces a fresh container with all eighteen config objects at their constructor defaults. `RestAssuredConfig.newConfig()` is byte-for-byte the same method under a second name. - `io.restassured.RestAssured.config()` is a **static accessor**. Its body reads the public field `RestAssured.config` and returns it, falling back to a new `RestAssuredConfig` only if that field is null. It hands you the suite's current configuration, not a blank one. The reason this bites is that the library's own documentation static-imports `config()` from `RestAssuredConfig`, so a bare `config()` in a snippet is the factory. Copy that snippet into a setup method that runs second and it quietly resets the suite. Name the declaring type whenever the difference matters. ## What immutability means here `RestAssuredConfig` holds its eighteen objects in a map keyed by config type, and it never exposes a mutator. Each of the eighteen setters — `redirect(...)`, `httpClient(...)`, `logConfig(...)`, `decoderConfig(...)` and the rest — builds and returns a **new** `RestAssuredConfig` whose other seventeen entries are copied straight across. The individual config classes work the same way: `redirectConfig().maxRedirects(3)` returns a new `RedirectConfig`, it does not edit one. Two consequences follow, and both are commonly missed: - **A chain that is not assigned does nothing.** `RestAssured.config.redirect(redirectConfig().maxRedirects(2));` compiles, allocates a configured object, and drops it on the floor. The compiler will not warn you. - **A chain never loses its own history.** `config().decoderConfig(...).httpClient(...)` keeps both, because the second setter copies the `DecoderConfig` the first one installed. ## Accumulate or reset — pick deliberately | Expression | Starts from | Net effect | |---|---|---| | `RestAssured.config = RestAssuredConfig.config().httpClient(x)` | all eighteen at default | everything set earlier is discarded | | `RestAssured.config = RestAssured.config().httpClient(x)` | the currently assigned config | `x` is added, the other seventeen survive | | `RestAssured.config.httpClient(x)` | the currently assigned config | nothing — the result is never stored | Both of the first two forms are legitimate. Reset-from-factory is what you want in a single place that owns the whole configuration, typically one suite-wide setup hook that runs before anything else. Accumulate-from-accessor is what you want everywhere else — and "everywhere else" is most real suites, because configuration tends to arrive from a base test class, a tagged setup method, and an environment-specific hook. ## How the bug looks in a marina suite The cost of getting this wrong is not a compile error or an exception; it is a setting that is present in the source and absent at runtime. A base class sets the response decoders the marina service needs. A second setup method, added months later by someone reading the wiki, writes `RestAssured.config = config().httpClient(httpClientConfig().reuseHttpClientInstance());`. Nothing fails loudly. The decoder setting is simply back at its default, and the symptom surfaces somewhere else entirely — a `GET /berths` response body that no longer decodes the way the assertions expect, in a test that never mentions configuration. That is the shape of every instance of this bug: - the failure is in a different file from the cause; - it appears only when both setup methods run, so a single test in isolation passes; - ordering decides the outcome, so the same suite can be green locally and red in CI; - nothing in the stack trace names `RestAssuredConfig`. ## Rules that keep it out 1. Assign configuration in exactly one place per scope, and let that place be the only user of `RestAssuredConfig.config()`. 2. Everywhere else, write `RestAssured.config = RestAssured.config().someConfig(...)` so the assignment reads as an addition. 3. Never write a bare `config()` in shared setup code; spell the class, so a reviewer can see which of the two you meant. 4. When you add to a config object that is already configured, chain from the existing instance — `RestAssured.config().getHttpClientConfig()` — rather than from a fresh `httpClientConfig()`, because the setter replaces the whole object. 5. If a setting seems not to take effect, check first that the chain was assigned to something, and second that the assignment was not overwritten by a later one. 6. Log the resolved configuration once at suite start if the suite is large enough that nobody can hold the setup order in their head. The fourth rule is the one that survives the first three: getting the container-level call right still leaves the object-level trap, because `httpClient(httpClientConfig().reuseHttpClientInstance())` swaps in a `HttpClientConfig` that knows nothing about the parameters the previous one carried.
- What does RestAssured.config.redirect(redirectConfig().maxRedirects(2)) do on its own?Nothing observable. `RestAssuredConfig` is immutable, so `redirect(...)` builds a new instance carrying the other seventeen config objects plus the new `RedirectConfig`, and returns it. With no assignment the returned instance is unreachable and the field still holds the old configuration. The statement compiles and allocates, which is why it survives review so often.
- Is there any difference between RestAssuredConfig.config() and RestAssuredConfig.newConfig()?No. Both are static methods on `RestAssuredConfig` whose body is `return new RestAssuredConfig();`, so both produce a fresh container with all eighteen objects at their defaults. Two names exist purely for readability: `newConfig()` reads better where the intent is to start over, `config()` where the intent is to build one inline. Either can be statically imported.
It is the difference between opening a fresh document and opening the one you were working on. Both give you something to type into; only one still has yesterday's paragraphs in it.
saying these in an interview costs you the question
- Thinks the two config() methods are the same call
- Says a chained setter edits the existing config in place
- Assigns RestAssured.config from a fresh factory in every setup method
- Calls a setter without assigning the returned config anywhere
- Believes untouched config objects are dropped when you chain a setter