38/60 = 63%. Zero blanks (was 2). 1h 8m 59s — dead centre of
the 60–75 minute band (was 1h 59m). MVC recovered 60% → 89% and Security rose to 75%. Every
instruction you acted on paid out.
But the score moved one mark, because the third instruction — rule every option true or false —
went the other way. Over-selection rose from 14 to 17 and now accounts for 77% of every
mark you lost. This page is almost entirely about that.
The section sizes above are exact, not estimated. Twenty-two misses distributed as
24/8/9/6/4/9 questions reproduce all six official percentages precisely (15/24 = 62.5% → 63%, 2/6 = 33.3% → 33%,
8/9 = 88.9% → 89%, and so on) and sum to 60. Spring Core is 24 of the 60 questions — 40% of the paper, and
the section with no clinic.
Testing is 6 questions and you got 2. At that size every single miss is worth 17 percentage
points, which is why it swings so violently — but 33% is still its worst showing in fourteen papers.
| Instruction after #13 | Attempt #13 | Attempt #14 | Result |
|---|---|---|---|
| Never leave a box blank | 2 blank | 0 blank | Done — 60/60 answered |
| Pace at 60–75 minutes | 1h 59m 36s | 1h 8m 59s | Done — 69 min, mid-band |
| Re-drill the decayed MVC clinic | MVC 60% | MVC 89% | ▲ 29 points |
This matters more than the score did. Three specific, testable changes were named after the last paper; you made all three; all three moved in the predicted direction. The method works — you now have direct evidence of it on your own paper. Security also rose to 75%, its best since attempt #4.
The score barely moved because the fourth instruction — the per-option true/false verdict — is the one that carries almost all the marks, and it is the one that did not change. That is the entire remaining gap.
You need +8. This single table is worth 9. Every row is a question where how many boxes to tick was knowable before you read a single option.
| Q | Stem says | You ticked | The extra tick |
|---|---|---|---|
| Q22 | "Which two statements are true…" | 3 | "Spring MVC starts an in-memory database by default" |
| Q38 | "Which three statements are advantages…" | 4 | "DI couples behavior with construction" |
| Q50 | "Which two statements are true…" | 3 | "@DataJpaTest tests JPA and NoSQL" |
| Q4 | Single answer — "What is true about @AliasFor?" | 2 | "an alias for a bean" |
| Q20 | Single answer — which scope is per-ServletContext | 2 | singleton |
| Q29 | Single answer — when is BeanPostProcessor called | 2 | "Only once — before init callbacks" |
| Q42 | Single answer — the correct pointcut expression | 2 | @execution(...) |
| Q49 | Single answer — what # references in SpEL | 2 | "Properties in the application environment" |
| Q57 | Single answer — how to initialise MockMvc | 2 | standaloneSetup(PersonController.class) |
Six of those nine are single-answer questions where you ticked two. On a single-answer question two ticks is always a zero — even when one of them is right. You are not hedging your bet; you are discarding a mark you had already earned.
The three "which N" questions are worse still, because the stem hands you the answer count for free. Q38 says three and you took four. Q22 and Q50 say two and you took three. Reading the stem's number and counting your ticks before submitting is a three-second habit worth 3 marks on this paper alone.
And look at the extras. Not one of them is a
close call: an in-memory database that needs JPA on the classpath, a DI "advantage" that says DI couples
things, @DataJpaTest doing NoSQL, an @execution designator that does not exist.
These are options you would reject instantly if you were forced to rule them individually — which is
exactly what the verdict habit forces.
| Failure mode | #12 | #13 | #14 | Trend |
|---|---|---|---|---|
| Over-selection — one wrong extra | — | 14 | 17 | Worse — now 77% of all misses |
| Under-selection — stopped short | — | 7 | 5 | Improving |
| Left blank | — | 2 | 0 | Fixed |
| Ticked only wrong options | — | 0 | 0 | Still zero |
For the second paper running, there is not one question where you ticked only wrong options. On all 22 misses you identified correct material. Your Spring knowledge is not what is failing this exam.
The five under-selections have a tell of their own:
on Q19, Q31, Q35, Q40 and Q54 you correctly rejected every false option — the fabricated
ReadOnlyRepository, the "VM password", the Config endpoint. Your discrimination is
sound in both directions. What fails is only the arithmetic at the end.
A new record (7 on #13, 4 on #12). The first three rows are the ones to sit with.
| Q | Topic | Already on this site | Age |
|---|---|---|---|
| Q49 | #{} SpEL vs ${} placeholder | Review #13 §③ — with a memory hook and 2 drills | 2 days |
| Q58 | Where @Value can be placed | Review #13 §③ — "fields, setters, constructor params…" | 2 days |
| Q26 | @Mock vs @MockBean — which library | Review #13 §⑤ — the exact three-row table | 2 days |
| Q9 | InitializingBean has no postConstruct | Review #12 Q6 — same wrong option | 10 days |
| Q50 | @DataJpaTest scope | Testing clinic · Review #12 closing list | 10 days |
| Q11 | Transaction propagation | Data clinic — the seven-level table | 4 weeks |
| Q40 | PagingAndSortingRepository | Data clinic · lesson 10 | 4 weeks |
| Q35 · Q54 | Actuator info + endpoint roster | Boot clinic — the roster table | 4 weeks |
Q49, Q58 and Q26 are the cleanest evidence this series has produced. Two days ago you asked for a drill page. It was built, it went live, and it contains a table and a memory hook for each of these three. You then met all three on a paper — and missed all three. Having the page is not the same as having drilled it.
Note how you missed them, though: Q49 and Q58 were both over-selections, and on Q58 you ticked five of the six true options and added the one false one. You knew the material from that page. The page worked; the counting habit is what wasn't there.
| Bucket | Marks | What it costs you |
|---|---|---|
| Count your ticks against the stem | 9 | Zero study. Six single-answer questions with 2 ticks, three "which N" with N+1 |
| Spring Core — the section with no clinic | 9 | 24 of 60 questions live here. SpEL, lifecycle, scopes, AOP, @Value |
| Re-drill the eight repeats | 9 | Material you already own; three of it two days old |
| Testing — 4 of its 6 questions | 4 | Smallest section, biggest swing. @DataJpaTest, MockMvc setup, @Rollback |
The first row alone clears the gap. You need +8 and the counting habit is worth 9 — on this paper, with no new Spring knowledge whatsoever. Everything below it is insurance.
| Q | Topic | What went wrong | The answer |
|---|---|---|---|
| Q4 | @AliasFor | + "an alias for a bean" | Aliases annotation attributes |
| Q9 | Lifecycle callback interfaces | + an InitializingBean method name | afterPropertiesSet() · destroy() |
| Q10 | JdbcTemplate return types | + JSONObject | Scalars · Map · RowMapper types |
| Q11 | Defining transaction propagation | + an invented property / env var | @Transactional(propagation=…) · TransactionDefinition |
| Q19 | @Transactional + @Rollback in tests | Under-selected — 4 were true | Rollback is the default; @Rollback is redundant |
| Q20 | Scope tied to the ServletContext | + singleton | application |
| Q22 | Boot + Spring MVC — "which two" | Ticked 3 — + in-memory DB | Embedded container by default · replaceable with Undertow |
| Q23 | What affects component scanning | + "ComponentScanner bean" — invented | scanBasePackages · <context:component-scan> |
| Q26 | Which are Boot-specific test annotations | + @InjectMocks (Mockito) | @SpringBootTest · @MockBean |
| Q27 | Environment in non-web apps | + a servlet-only source | System properties · env vars · profiles |
| Q29 | When BeanPostProcessor runs | + "only once" | Twice per bean, around the init callbacks |
| Q31 | RestTemplate custom headers | Under-selected — 4 were true | Use exchange(); getForEntity takes no HttpEntity |
| Q32 | @PreAuthorize expressions | + @username (bean prefix) | #username · hasRole('ADMIN') |
| Q35 | What to expose via Actuator info | Under-selected — 4 were true | Version · git hash · description · name |
| Q38 | Advantages of DI — "which three" | Ticked 4 | Loose coupling · centralised config · external management |
| Q40 | Spring Data repository interfaces | Under-selected — 3 were true | Repository · CrudRepository · PagingAndSortingRepository |
| Q42 | Pointcut for getters and setters | + @execution — invented | execution(* …get*()) || execution(* …set*(*)) |
| Q49 | What # references in SpEL | + "environment properties" | Spring beans — #{@beanName…} |
| Q50 | @DataJpaTest — "which two" | Ticked 3 — + NoSQL | TestEntityManager · embedded DB if on classpath |
| Q54 | Which are Actuator endpoints | Under-selected — 3 were true | conditions · httptrace · beans |
| Q57 | Initialising MockMvc | + the .class variant | standaloneSetup(new PersonController()) |
| Q58 | Where @Value can be used | + method param without @Autowired | Field · method · constructor param · annotation type |
@Value — 2 misses (both 2 days old)
② Container, lifecycle & scopes — 4 misses
③ Annotations & DI — 2 misses
④ AOP pointcuts — 1 miss
⑤ Testing — 4 misses (33%)
⑥ Spring Boot — 4 misses
⑦ Data Management — 3 misses
⑧ Spring MVC — 1 miss (89%)
⑨ Spring Security — 1 miss (75%)
Every question below is one you met on this paper. Before you look at the options, decide from the stem how many boxes you are allowed to tick. Then rule each option true or false on its own merits and take exactly the true ones. This set exists to make that sequence automatic.
| Stem says | Ticks allowed | What a wrong count costs |
|---|---|---|
| "Which two…" / "Which three…" | Exactly that many | N+1 ticks = zero, even with all N right |
| "What is…" / "Which option…" / a fill-in-the-blank | Exactly one | 2 ticks = zero, even when one is correct |
| "Which of these are…" / "Which statements are correct" | However many are true — often all | Stopping early loses it; adding one loses it |
The asymmetry worth internalising: on a single-answer question, a second tick can only ever destroy a mark. It cannot rescue one. If you are torn between two options, ticking both scores 0; committing to either scores 0 or 1. Guessing beats hedging every single time.
On an open multi-select, the redundancy tell: when several options restate the same idea from different angles, they are usually all true — that is how this bank writes "everything is correct" questions. Q19, Q31 and Q35 on this paper were all of that shape.
@Value 2 misses · both explained here 2 days ago# references in SpEL over-selected review #13 · 2 days#{...} is the SpEL delimiter; ${...} is the property placeholderReview #13 §③ carries this exact table. Here it is again, extended with what each prefix reaches:
| Syntax | Is | Resolved by | Reaches |
|---|---|---|---|
${...} | Property placeholder | The Environment | Application properties — the option you wrongly ticked |
#{...} | SpEL expression | The expression parser | Beans, statics, literals, arithmetic |
#{@beanName.method()} | Bean reference | SpEL + the context | Spring beans — the correct answer |
#{systemProperties['k']} | SpEL over a built-in | SpEL | JVM system properties |
#{T(Math).PI} | T() type operator | SpEL | Static fields and methods |
Why the wrong tick is seductive and still wrong: SpEL can read
properties — but only by going through a built-in like systemProperties, or by nesting a placeholder
(#{${app.map}}). A bare # does not reference environment properties; that is what
$ is for. The two prefixes are the whole point of the question.
Note the second use of #, which appears on this paper in Q32: inside a Spring Security
expression, #name refers to a method parameter and @name refers to a bean.
Same symbols, different context — see §⑨.
${} = property, from the Environment. #{} = SpEL. #{@bean} = a bean. A bare # never means "a property".@Value can be used over-selected review #13 · 2 days@Autowired| Placement | Works? | Why |
|---|---|---|
| On a field | Yes | Injected by AutowiredAnnotationBeanPostProcessor, reflection bypasses private |
| On a method | Yes | On a setter or any @Autowired method |
| On a constructor parameter | Yes | A single constructor is used implicitly — no @Autowired needed |
On a method parameter, with @Autowired on the method | Yes | The method is an injection point, so its parameters are processed |
| On an annotation type | Yes | @Target includes ANNOTATION_TYPE — composite annotations |
On a method parameter, without @Autowired | No | Spring never calls the method, so the parameter is never resolved |
The principle behind the one exception: @Value is not magic — it is
resolved at an injection point. A field is an injection point. A constructor is an injection point (the
single-constructor rule). An @Autowired method is an injection point. A plain method is not — so
Spring never invokes it, and its parameter annotations are dead code. No exception is thrown; the method is
simply never called.
The other silent failure worth carrying: @Value("${missing.key}")
throws IllegalArgumentException at startup. Supply a default with the colon syntax:
@Value("${missing.key:fallback}").
@Value works wherever Spring already injects — field, constructor param, @Autowired method (and its params), annotation type. A bare method is not an injection point.afterPropertiesSet() and destroy()InitializingBean a method called init
or postConstruct. The identical wrong option cost you Q6 on
attempt #12.| Route | Init | Destroy | Origin |
|---|---|---|---|
| Interface | InitializingBean.afterPropertiesSet() | DisposableBean.destroy() | Spring |
| Annotation | @PostConstruct | @PreDestroy | JSR-250, not Spring |
| Config attribute | @Bean(initMethod="…") / XML init-method | @Bean(destroyMethod="…") | Spring |
Why postConstruct keeps looking plausible: @PostConstruct
is real and does the same job at the same moment — but it is an annotation from JSR-250, not a method on
a Spring interface. The exam repeatedly offers the annotation's name as an interface method. The interface
method is afterPropertiesSet(), and there is no init method on
InitializingBean.
Order, if all three routes are present on one bean:
@PostConstruct → afterPropertiesSet() → custom init-method. Destruction
mirrors it: @PreDestroy → destroy() → custom destroy-method.
Annotation first, interface second, custom method last.
InitializingBean.afterPropertiesSet() · DisposableBean.destroy(). @PostConstruct is a JSR-250 annotation, never an interface method.BeanPostProcessor runs over-selectedWhy "only once" is not a near-miss but a serious error: the second hook,
postProcessAfterInitialization, is where Spring wraps your bean in an AOP proxy
(AbstractAutoProxyCreator). Drop that hook and @Transactional, @Async,
@Cacheable and every custom aspect stop existing. It is the most consequential callback in the
container.
The full order for a single bean — worth knowing as a sequence, because the exam asks it from several angles:
| # | Step |
|---|---|
| 1 | Constructor / factory method — the instance exists |
| 2 | Dependency injection (populate properties) |
| 3 | Aware setters — BeanNameAware, BeanFactoryAware… |
| 4 | BeanPostProcessor.postProcessBeforeInitialization |
| 5 | @PostConstruct → afterPropertiesSet() → custom init-method |
| 6 | BeanPostProcessor.postProcessAfterInitialization ← proxy created here |
| 7 | Bean is ready for use |
| 8 | On shutdown: @PreDestroy → destroy() → custom destroy-method |
Two facts that answer the distractors: instantiation and DI have already
happened before any BPP runs — so "before any bean is created" is wrong. And destruction is not part of
this interface: that is DestructionAwareBeanPostProcessor, a sub-interface — so "three times"
is wrong. The base interface has exactly two methods.
A neat consequence: @PostConstruct at step 5 is itself implemented by a
BeanPostProcessor (CommonAnnotationBeanPostProcessor). The mechanism runs the annotation.
postProcessAfterInitialization is where the AOP proxy is born. Destruction lives in a different interface.ServletContext over-selectedapplication — not singleton, and the difference is realsingleton alongside application.
Understandable — they behave identically in a simple app. The stem's words
"ServletContext" and "web-aware" are what separate them.| Scope | Lifecycle | Web only? | Instances |
|---|---|---|---|
singleton (default) | The IoC container | No | 1 per ApplicationContext |
prototype | On demand | No | A new one every injection |
request | One HTTP request | Yes | 1 per request |
session | One HTTP session | Yes | 1 per user session |
application | The ServletContext | Yes | 1 per web application |
websocket | One WebSocket session | Yes | 1 per WS session |
Where they actually diverge. A singleton is one per
ApplicationContext. An application-scoped bean is one per ServletContext. In an app
with a root context plus one or more DispatcherServlet child contexts, each context gets its own
singleton, but they all share one application-scoped bean — it is stored as a ServletContext
attribute, and is reachable via servletContext.getAttribute() from outside Spring entirely.
The exam tell: the words ServletContext and web-aware in the
stem. Neither can apply to singleton, which works in any context type. When a stem names a
servlet concept, a non-web scope cannot be the answer.
Environment in non-web applications over-selectedStandardEnvironment has exactly two property sources — everything servlet is absentThe question is a two-class comparison, and once you see that, every option sorts itself:
| Available | Class | Backed by |
|---|---|---|
| JVM system properties | StandardEnvironment | System.getProperties() |
| OS environment variables | StandardEnvironment | System.getenv() |
| Profiles | The Environment interface itself | getActiveProfiles() / getDefaultProfiles() |
| JNDI properties | StandardServletEnvironment | Needs a container |
| ServletContext parameters | StandardServletEnvironment | <context-param> |
| ServletConfig parameters | StandardServletEnvironment | ServletConfig.getInitParameter() |
Profiles are the easy one to under-think: they are not a property source at all —
they are part of the Environment interface, so they are available in every context type,
web or not.
One relaxed-binding trap worth carrying:
SystemEnvironmentPropertySource matches case-insensitively and ignores separators, so
MY_VAR, my.var and my-var all resolve to the OS variable
MY_VAR.
StandardEnvironment = system properties + env vars, plus profiles from the interface. Anything with "Servlet" or "JNDI" in the name needs StandardServletEnvironment.@AliasFor over-selected@AliasFor declares that two attributes of the same annotation are the same
attribute under two names. Set either and both read back identically; set both to different values and Spring
throws.
public @interface MyAnnotation {
@AliasFor("location") String value() default "";
@AliasFor("value") String location() default "";
}
// @MyAnnotation("/tmp") and @MyAnnotation(location = "/tmp") are identical
Where you have already used it without noticing: it is why
@RequestMapping("/x") means path = "/x", why
@SpringBootTest(classes = …) works, and why composed annotations can rename an attribute of the
annotation they meta-annotate.
Bean aliasing, for contrast — the feature the distractor borrows its words from:
@Bean(name = {"primary", "alias1"}), or <alias name="a" alias="b"/> in XML.
Different mechanism, different layer. The stem said annotation attributes.
@AliasFor aliases attributes within an annotation. Bean names are aliased by @Bean(name={...}). Different layers.Read the option's own reasoning. It claims an advantage of DI on the grounds that DI couples behavior with construction — but coupling is exactly what DI removes. The option argues against itself. And its conclusion is backwards too: because the wiring happens in the container rather than at the call site, DI generally makes code harder to trace, not easier. That is one of DI's genuine costs.
| Statement | Verdict | Why |
|---|---|---|
| Facilitates loose coupling | TRUE | Components depend on abstractions, wired at runtime |
| Configuration externalized and centralized | TRUE | Java config, XML or properties in one place |
| Dependencies managed external to components | TRUE | The container owns construction and lifecycle |
| Easier to trace — couples behavior with construction | FALSE | DI decouples them; tracing usually gets harder |
| Creates tight coupling | FALSE | The direct opposite of DI's purpose |
| Reduces start-up time | FALSE | Wiring the container is overhead, if anything |
A reusable filter for "advantages of X" questions: an option that states a real property of X and then calls it an advantage is often the trap — and an option whose stated mechanism contradicts X's definition is always false. Two of the three false options here are simply DI's definition inverted.
@execution — the designator is execution, unprefixedexecution(...) option and the
@execution(...) one. Single answer — and the @ prefix is the entire
difference between them.The @ prefix has one meaning in AspectJ: "match by annotation".
Every @-designator takes an annotation type as its argument, never a method signature. So
@execution(* com.beans.EmployeeBean.get*()) is doubly wrong — the designator does not exist, and the
argument would be the wrong kind of thing even if it did.
| Designator | Argument | Matches |
|---|---|---|
execution(pattern) | Method signature | Return type, class, method name, params |
within(TypePattern) | Type pattern | Every method in matching types |
bean(namePattern) | Spring bean name | All methods on that bean |
@annotation(Anno) | Annotation type | Methods carrying that annotation |
@within(Anno) | Annotation type | Methods in classes carrying it |
| — | Does not exist |
| — | Does not exist |
Now the wildcards, which the correct answer turns on:
| Pattern | Parameters matched |
|---|---|
get*() | Zero arguments — correct for getters |
set*(*) | Exactly one argument — correct for setters |
get*(..) | Any number, including zero — all overloads |
@Pointcut("execution(* com.beans.EmployeeBean.get*())") public void getters() {}
@Pointcut("execution(* com.beans.EmployeeBean.set*(*))") public void setters() {}
@Before("getters() || setters()")
public void logAccess(JoinPoint jp) { ... }
Carry this alongside the pointcut facts from review
#12: (..) in the parameter position means any arguments;
.. in a package position means that package and below; * matches one segment.
@-designator always takes an annotation type. execution takes a signature and has no @. get*() = no args · set*(*) = one arg · (..) = any.Testing is only 6 questions, so each one is worth 17 percentage points — but 2/6 is still the lowest it has been in fourteen papers, and it has now fallen for two consecutive attempts since its clinic was written. Three of these four are over-selection.
@InjectMocks alongside the two correct ones.
You correctly rejected @Mock — which is from the same library as
@InjectMocks. That inconsistency is the tell.Review #13 §⑤ carries this table. The question is never "do I use it in Spring tests?" — you use all of them in Spring tests. It is "which jar does it come from?":
| Annotation | Library | Purpose |
|---|---|---|
@SpringBootTest | spring-boot-test | Loads a full Boot ApplicationContext |
@MockBean | spring-boot-test | Mock that replaces a bean in the context |
@SpyBean | spring-boot-test | Spy that wraps the real bean |
@Mock | mockito-core | Plain Mockito mock — Spring never sees it |
@Spy | mockito-core | Plain Mockito spy |
@InjectMocks | mockito-core | Injects @Mocks into the object under test |
The clean split: every Spring Boot test annotation has "Bean" or "SpringBoot"
in its name — @MockBean, @SpyBean, @SpringBootTest. The Mockito ones
are the bare nouns: @Mock, @Spy, @InjectMocks. That is a reliable
surface-level filter for this question.
Why the distinction has teeth: Mockito annotations need
@ExtendWith(MockitoExtension.class) (or openMocks(this)) and never touch the Spring
context. @MockBean needs a Spring context and replaces a bean inside it — which also means it
changes the context definition and forces a new cached context.
@MockBean, @SpyBean). Bare noun = Mockito (@Mock, @Spy, @InjectMocks).@DataJpaTest — "which two" ticked 3 Testing clinicJdbcTemplate| Statement | Verdict | Why |
|---|---|---|
Auto-configures a TestEntityManager | TRUE | With helpers like persistFlushFind |
| Uses an embedded DB if one is on the classpath | TRUE | H2 / HSQL / Derby, replacing the real DataSource |
| Tests JPA and NoSQL | FALSE | NoSQL has its own slices: @DataMongoTest, @DataRedisTest |
Can be used to test JdbcTemplate | FALSE | That is @JdbcTest |
TestEntityManager has all EntityManager methods and more | FALSE | A useful subset, plus test helpers |
The slice principle, which answers all of these at once: a slice loads exactly one technology's layer and nothing else. So there is a separate slice per technology — if the option names a second technology, it is false.
| Slice | Loads | Also |
|---|---|---|
@DataJpaTest | JPA repositories, entities, TestEntityManager | Transactional, rolled back; embedded DB |
@JdbcTest | JdbcTemplate, DataSource | Transactional, rolled back |
@DataMongoTest | Mongo repositories + template | Embedded Mongo if present |
@WebMvcTest | Controllers, advice, @JsonComponent, MockMvc | No services, no repositories |
@JsonTest | Jackson/Gson config + testers | No web layer |
@DataJpaTest = JPA only, embedded DB, transactional and rolled back. NoSQL → @DataMongoTest · JdbcTemplate → @JdbcTest.MockMvc standalone over-selectedstandaloneSetup takes an instance, not a ClassstandaloneSetup(new PersonController()) — correct — and
standaloneSetup(PersonController.class). Single answer, and the difference
is one keyword.Why it must be an instance. Standalone mode has no Spring context — that
is its entire point. With no container there is nothing to instantiate a Class or inject its
dependencies, so you construct the controller and hand over the finished object, wiring any collaborators
yourself (usually with plain Mockito mocks).
| Builder | Takes | Loads a context? | Use for |
|---|---|---|---|
MockMvcBuilders.standaloneSetup(controller…) | Controller instances | No | Fast, isolated controller unit tests |
MockMvcBuilders.webAppContextSetup(wac) | A WebApplicationContext | Yes | Integration tests — null is an error |
@AutoConfigureMockMvc + @Autowired MockMvc | — | Yes | Boot tests; the container builds it |
PersonService service = Mockito.mock(PersonService.class);
mockMvc = MockMvcBuilders
.standaloneSetup(new PersonController(service)) // an INSTANCE you built
.build();
And new MockMvc() is never right: MockMvc has no public
constructor. It only ever comes from a MockMvcBuilders builder or from
@AutoConfigureMockMvc.
The trade-off worth knowing: standalone mode skips
@ControllerAdvice, filters, and converters unless you register them on the builder
(.setControllerAdvice(...), .addFilters(...)). That speed comes at the cost of
fidelity.
standaloneSetup(new Controller(deps)) — an instance, no context. webAppContextSetup(wac) — a real context. new MockMvc() never compiles.@Transactional + @Rollback in tests under-selectedThe one fact the question is built on: a @Transactional test method
is rolled back automatically. @Rollback is @Rollback(true), which is already the
default — so writing it changes nothing. It is redundant, not wrong.
| Combination | Effect |
|---|---|
@Transactional alone | Rolled back — the default |
@Transactional + @Rollback | Rolled back — redundant but harmless |
@Transactional + @Rollback(false) | Committed — changes persist |
@Transactional + @Commit | Committed — a readable alias for the above |
No @Transactional | No test-managed transaction; whatever the code does, commits |
Working through the four true statements, since each is a separate idea:
| Statement | Why it is true |
|---|---|
@Rollback(false) would make testRollback fail | The update would commit, so the later assertion of 0 breaks |
testRollback verifies the changes did not persist | That is exactly what asserting 0 afterwards checks |
@Rollback is not required | Rollback is already the default for a transactional test |
| The transaction rolls back, leaving inventory unchanged | The direct consequence of the default |
The false pair, for completeness: testRollback does not need
@Transactional — it only reads committed state. And dropping @Transactional is not how
you commit; that is @Rollback(false).
A real caveat the question glosses over: this test pair depends on execution
order, which JUnit 5 does not guarantee. In practice use
@TestMethodOrder(MethodOrderer.OrderAnnotation.class), or have each test set up its own state.
@Rollback = redundant · @Rollback(false) / @Commit = persist.Boot auto-configuration is conditional, never unconditional. An embedded database
appears only when both a data starter (spring-boot-starter-data-jpa or
-jdbc) and an embedded driver (H2, HSQLDB, Derby) are on the classpath. A pure Spring MVC app
with neither gets no DataSource at all.
| Statement | Verdict | The fact |
|---|---|---|
| Boot starts an embedded servlet container by default | TRUE | Tomcat, unless you exclude it |
| The container can be replaced with Undertow | TRUE | Swap the starter — Jetty and Undertow both work |
| Spring MVC starts an in-memory DB by default | FALSE | Needs a data starter and an embedded driver |
| The default port is 8088 | FALSE | 8080. 8088 is not a Spring default anywhere |
| Jetty is the default container | FALSE | Tomcat is |
The three embedded containers, and how you choose: Tomcat (default), Jetty,
Undertow — always by dependency, never by a property. Exclude
spring-boot-starter-tomcat and add the one you want. To run with no web server at all:
spring.main.web-application-type=none.
ComponentScanner bean that does not existComponentScanner bean" with the two correct
options. You correctly rejected the invented spring.scan.location property — but
took the invented bean instead.Both wrong options are fabrications, and they fail the same test: component
scanning is decided at configuration time, by an annotation attribute or an XML element. It is never
driven by a runtime bean or by an application.properties key — the scan has to happen before
beans and properties exist.
| Mechanism | Real? | Form |
|---|---|---|
@SpringBootApplication(scanBasePackages=…) | REAL | Overrides the default root package |
@ComponentScan(basePackages=…) | REAL | Plain Spring Java config |
<context:component-scan base-package="…"/> | REAL | XML config |
@ComponentScan(basePackageClasses=…) | REAL | Type-safe — refactor-proof, worth preferring |
bean with a location field | INVENTED | No such class |
| INVENTED | No such property |
The default, which the exam also asks: with no attribute at all,
@SpringBootApplication scans its own package and everything below it. That is why the main
class belongs at the root of your package tree — a class in a sibling package is never found.
The naming tell, again: ComponentScanner is the everyday-English
noun for the thing @ComponentScan does — the same shape as @SqlScript for
@Sql and @Property for @Value. The invented option is the one that
sounds more descriptive than the real name.
@SpringBootApplication package and below.info under-selected Boot clinicThis question has an unusually simple rule: /actuator/info is
arbitrary public build and app metadata. Anything descriptive is true; anything secret is false. There is
no third category.
| Datum | Expose? | Contributor |
|---|---|---|
| Application version | Yes | BuildInfoContributor / info.app.version |
| Application name | Yes | EnvironmentInfoContributor |
| Application description | Yes | EnvironmentInfoContributor |
| Git commit hash / branch | Yes | GitInfoContributor ← git.properties |
| VM password | Never | — |
| Database password | Never | — |
Two version traps the clinic flags: since Boot 2.6 the
EnvironmentInfoContributor is disabled by default — your info.* properties will
not appear unless you set management.info.env.enabled=true. And info is not exposed
over HTTP by default in Boot 2.x; you need
management.endpoints.web.exposure.include=health,info.
Treat /info as public. Unless you secure the management endpoints it
is unauthenticated — so no internal hostnames, no stack traces, and certainly no credentials.
info = public build metadata: name, description, version, git commit. Never a secret. Boot 2.6+ needs management.info.env.enabled=true.Config is inventedConfig.| Endpoint | Real? | Shows |
|---|---|---|
conditions | REAL | The auto-configuration report — what applied and why |
httptrace | REAL | Recent HTTP exchanges (httpexchanges in Boot 3) |
beans | REAL | Every bean, its type, scope and dependencies |
| INVENTED | The nearest real ones are env and configprops |
The roster worth holding whole, since this is now the third Actuator question in two papers:
| Endpoint | Purpose |
|---|---|
health · info | Health status · build metadata — the only two exposed over HTTP by default… and only health in 2.x |
beans · conditions · mappings | The bean graph · the auto-config report · every @RequestMapping URL |
env · configprops | Raw property sources · bound @ConfigurationProperties values |
metrics · loggers | Micrometer meters · read and write log levels at runtime |
threaddump · heapdump · logfile | The last two are web-only; threaddump is JSON and works over JMX |
shutdown | The only endpoint disabled by default |
The distinction that has cost marks in four papers: enabled and
exposed are different switches. Nearly every endpoint is enabled; only
shutdown is not. Exposure is separate: JMX exposes *, HTTP exposes only
health by default.
conditions, httptrace, beans are all real. Enabled ≠ exposed: only shutdown is disabled; only health is HTTP-exposed by default.JdbcTemplate query return types over-selectedJSONObject is not one of themJSONObject with the three correct options.
You rejected XMLObject and Properties — but
JSONObject is the same kind of option.The layer test. JdbcTemplate speaks JDBC: it reads a
ResultSet of columns and rows. JSON is a serialization concern that lives at the web layer
(Jackson), not the data layer. JSONObject is not even a JDK type — it comes from an external JSON
library. If an option names a format from another layer, it is wrong.
| Family | Method | Returns |
|---|---|---|
| Scalars | queryForObject(sql, Integer.class) | int, long, String… |
| Generic maps | queryForMap(sql) · queryForList(sql) | Map<String,Object> — one row, column names as keys |
| Domain objects | query(sql, RowMapper<T>) | List<T> of your own type |
| JSONObject · XMLObject · Properties | — | Not JdbcTemplate return types |
List<Order> orders = jdbcTemplate.query("SELECT * FROM orders",
(rs, rowNum) -> new Order(rs.getLong("id"), rs.getString("status")));
RowMapper vs ResultSetExtractor, which the exam pairs
with this: RowMapper is called once per row and returns one object;
ResultSetExtractor is called once for the whole ResultSet and returns one
object — use it when rows must be aggregated, as with a join producing one parent and many children.
Maps, or your own types via RowMapper. JSON and XML are other layers. RowMapper = per row · ResultSetExtractor = per ResultSet.spring.propagation.mode property and/or a
SPRING_PROPAGATION_MODE environment variable. Both correct options were
taken.Why a global setting could not possibly work. Propagation is a per-method
decision — it describes what this method should do if a transaction is already running. An audit method
wants REQUIRES_NEW while the business method around it wants REQUIRED. A single
global value would make the whole concept meaningless, which is why no such property exists.
| Route | How |
|---|---|
| Declarative | @Transactional(propagation = Propagation.REQUIRES_NEW) |
| Programmatic | DefaultTransactionDefinition.setPropagationBehavior(...), or on a TransactionTemplate |
| A property | No such thing |
| An environment variable | No such thing |
A PropagationMode bean | No such type |
The seven levels — from the Data clinic, and the single most reusable table in this section:
| Propagation | No active transaction | Transaction already active |
|---|---|---|
REQUIRED (default) | Start a new one | Join it |
REQUIRES_NEW | Start a new one | Suspend it, start an independent one |
SUPPORTS | Run non-transactionally | Join it |
NOT_SUPPORTED | Run non-transactionally | Suspend it, run non-transactionally |
MANDATORY | Throw | Join it |
NEVER | Run non-transactionally | Throw |
NESTED | Start a new one | Create a savepoint inside it |
Read the pairs, not the seven rows. MANDATORY and
NEVER are the two that throw, in opposite situations. SUPPORTS and
NOT_SUPPORTED differ only in what they do with an existing transaction — join it, or suspend it.
Two practical traps: NESTED needs savepoint support, so it works
with a JDBC transaction manager but generally not with JTA. And REQUIRES_NEW takes a
second connection from the pool while the outer transaction still holds its own — a small pool can
deadlock.
@Transactional attribute, or a TransactionDefinition. No property, no env var. MANDATORY and NEVER are the two that throw.Repository markerReadOnlyRepository.The one that gets left behind is Repository<T, ID> itself,
because it has no methods and so does not feel like an interface you would use. But it is the root marker
Spring Data scans for — and extending it directly is exactly how you build a narrow, read-only repository that
exposes only the query methods you declare.
| Interface | Adds | Use for |
|---|---|---|
Repository<T, ID> | Nothing — a marker | A deliberately narrow / read-only API |
CrudRepository<T, ID> | save, findById, findAll, delete, count | Basic CRUD |
PagingAndSortingRepository<T, ID> | findAll(Pageable), findAll(Sort) | List views |
JpaRepository<T, ID> | flush, saveAndFlush, getReferenceById | JPA-specific extras |
| — | Does not exist — build it from Repository |
The fabricated name is worth pausing on: ReadOnlyRepository names a
real and common need, which is what makes it convincing. Spring Data's answer to that need is not a
dedicated interface — it is extend Repository and declare only what you want. When an option
names an interface for a use case rather than for a capability, be suspicious.
A version note for Boot 3: in Spring Data 3.0,
PagingAndSortingRepository no longer extends CrudRepository — you must extend
both if you want paging and CRUD. Your exam targets the 2.x hierarchy, where it does.
Repository (marker) → CrudRepository → PagingAndSortingRepository → JpaRepository. No ReadOnlyRepository — extend the marker instead.MVC's three clinic rounds took it to 100%, then it decayed to 57% and 60% once you stopped drilling. One re-drill brought it back to 89% with a single miss — and that miss is an under-selection, not a knowledge gap. This is the clearest demonstration yet that the decay is reversible and cheap to reverse. The same treatment is what Testing (33%) and Boot (56%) need now.
RestTemplate with custom headers under-selectedgetForEntity accepts an HttpEntity.The trick in the stem: the snippet looks like it sends the Bearer token,
but it does not. getForEntity(url, String.class, requestEntity) compiles because the third parameter
is a varargs of URI template variables — so the HttpEntity is silently used as a URI variable
and the Authorization header is never sent.
| Method | Takes an HttpEntity? | Custom headers? |
|---|---|---|
getForEntity(url, type, uriVars…) | No — the tail is URI variables | No |
getForObject(url, type, uriVars…) | No | No |
postForEntity(url, request, type) | Yes — as the body | Yes |
exchange(url, method, entity, type) | Yes | Yes — any HTTP method |
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + token);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
The four true statements, each a separate fact: HttpEntity wraps
headers and an optional body; HttpHeaders is how you set Authorization; the Bearer
format in the snippet is correct; and exchange is what you must use for a GET with
custom headers. Only "getForEntity accepts an HttpEntity" is false.
Worth noting the "correct but useless" pattern: the token is formatted correctly — that option is true — even though the request never carries it. Rule each statement against what it actually claims, not against whether the code works. That habit is what turns a 4-of-5 into a mark.
exchange(). getForEntity's trailing arguments are URI variables, so headers silently vanish.@PreAuthorize expressions over-selected# is a method parameter · @ is a bean — and the paper tested both today@PreAuthorize("@username == authentication.name") with the two
correct options. The same #/@ pair that cost you Q49.Two questions on this paper turn on the same two symbols. Q49 asked what
# means in plain SpEL; this asks what it means inside a security expression. Learn the pair once:
| Prefix | In a security expression | Example |
|---|---|---|
# | A method parameter, by name | #username == authentication.name |
@ | A Spring bean, then call a method on it | @permissionService.canEdit(#id) |
| no prefix | Built-in expressions and the root object | hasRole('ADMIN'), authentication.name |
Why @username fails: it asks Spring for a bean named
username. There is no such bean, so the expression throws at evaluation time — a runtime failure,
not a compile error. And @PreAuthorize("'ADMIN'") fails for a different reason: it is a
string literal, and the expression must evaluate to a boolean.
| Built-in | Checks |
|---|---|
hasRole('ADMIN') | Authority ROLE_ADMIN — the prefix is added for you |
hasAuthority('ADMIN') | Authority ADMIN exactly — no prefix added |
hasAnyRole(...) / hasAnyAuthority(...) | Any one of several |
isAuthenticated() / isAnonymous() | Authentication state |
principal / authentication | The current principal / full Authentication |
The practical gotcha with #param: parameter names are erased at
compile time unless you build with -parameters. Without it, annotate the parameter:
canEdit(@P("username") String username).
# = method parameter · @ = bean · bare = built-in. hasRole adds ROLE_; hasAuthority does not. The expression must return a boolean.@AliasFor aliases annotation attributes; bean names are aliased with @Bean(name={...})InitializingBean.afterPropertiesSet() · DisposableBean.destroy(). @PostConstruct is a JSR-250 annotation, never an interface method@PostConstruct → afterPropertiesSet() → custom init-method. Destroy mirrors itJdbcTemplate returns scalars, Maps, or your types via RowMapper. Not JSON, not XML, not PropertiesRowMapper = once per row · ResultSetExtractor = once per ResultSet@Transactional attribute or a TransactionDefinition. No property, no env varMANDATORY throws with no transaction · NEVER throws when there is one. NESTED = savepoint, REQUIRES_NEW = suspend + second connection@Rollback is redundant; @Rollback(false) / @Commit persistapplication scope = one per ServletContext; singleton = one per ApplicationContext. Web-only: request, session, application, websocketscanBasePackages, @ComponentScan or <context:component-scan>. No ComponentScanner bean, no spring.scan.location@SpringBootApplication package and below. Siblings are never found@Mock, @Spy, @InjectMocks are all MockitoStandardEnvironment = system properties + env vars, plus profiles from the interface. Anything "Servlet" or "JNDI" needs the servlet environmentBeanPostProcessor runs twice per bean, around the init callbacks. postProcessAfterInitialization is where the AOP proxy is createdRestTemplate method = exchange(). getForEntity's trailing args are URI variables# = method parameter · @ = bean · bare = built-in. hasRole adds ROLE_; hasAuthority does notinfo = public build metadata — name, description, version, git commit. Never a credentialRepository (marker) → CrudRepository → PagingAndSortingRepository → JpaRepository. No ReadOnlyRepository@-designator takes an annotation type. execution takes a signature and has no @. get*() = no args · set*(*) = one#{} = SpEL · ${} = property. @Value works at injection points only — a plain method parameter is not oneTen questions in exam order, with no topic label to prime you. Decide how many ticks the stem allows before you read the options.
ComponentScanner · @execution ·
spring.propagation.mode · SPRING_PROPAGATION_MODE · ReadOnlyRepository ·
Config. The fake is almost always the more descriptive, more everyday name.
(5) Re-drill Testing and Boot now. They are 33% and 56%, and MVC just proved a single re-drill is worth
~29 points.