41/60 = 68%. Spring MVC scored 100% for the second consecutive paper — it is genuinely finished. But this paper has an uncomfortable signature: four of the nineteen are questions you have already met and already missed, one of them for the third time. That's 4 marks sitting in material you own, against a gap of 5. This page is built for drilling rather than reading — the explanations are short and there are 30 questions.
Numbering note: you called this attempt 11, but #11 was the 29 Aug paper — this is #12.
Four of six sections went up, and MVC held its perfect score. Spring Core recovered from 58% to
70%, Security from 50% to 67%, Testing from 60% to 75%. Nothing regressed badly.
But Spring Boot is decaying. 86% → 75% → 61% across the three papers since its clinic —
and it now holds 7 of the 19 misses, level with Core. The clinic gain is wearing off exactly as the
earlier ones did without re-drilling.
The clock went back up. 40m 54s, from 35m on the last paper. Good — but still only 31% of
the time, with 89 minutes unused.
This is the thing to take away from this paper. These aren't similar questions — they are the same questions, with the same options, previously explained on this site.
| Q | Question | Previously | What you ticked this time |
|---|---|---|---|
| Q28 | Valid SpEL expressions | #7 Q53 · #8 Q21 — third time | (() => 2 * 3)(), the JavaScript arrow function, again |
| Q39 | Advice types accepting a JoinPoint | #6 Q22 | "None of the above" — the same wrong option |
| Q45 | Authentication mechanisms | #11 Q17 — yesterday | LDAP (a user store, not a mechanism) |
| Q53 | Web-only Actuator endpoints | Boot clinic §1 | threaddump — the trap the clinic names explicitly |
Q28 is the one to sit with. An option written in JavaScript has now been offered to you three times across three weeks, and you have ticked it three times. It is not a Spring question — it is a "which language is this?" question. Arrow functions are JavaScript. Java and SpEL have no such syntax.
What this says about method. All four were read in a review or clinic. None were retrieved under pressure. Reading builds recognition — you'd know the right answer if I asked you now — but the exam tests recall against four plausible options and a ticking clock. That's the gap the drills close, and it's why this page is 35 questions and short explanations.
MVC was 50% eight days ago and had never been above 75% in ten
attempts. After three
drill
rounds it has scored 100% on two consecutive papers with zero
misses. That is the first section to hold a perfect score twice, and it is the strongest evidence yet that the
clinic-then-drill loop works.
The counter-example is on the same page: Spring Boot got a clinic, jumped to 86%, and has fallen to 61%
across two papers without being re-drilled. Build, drill, then re-drill — the third step is the
one that's missing.
| Bucket | Marks | What it costs |
|---|---|---|
| ① Repeats already explained here Q28, Q39, Q45, Q53 |
4 | Drill them — don't re-read them. ~15 minutes. |
| ② Spring Boot re-drill Q1, Q4, Q11, Q29, Q50, Q53 all sit in the Boot clinic |
6 | Boot clinic, 45 drills. ~25 minutes. |
| ③ Invented names + polarity Q3 @Property · Q16 two fakes · Q24 "which is NOT" |
3 | Zero study. Two habits you already know. |
| ④ Genuinely new (transaction attributes, lifecycle interfaces, pointcut patterns, Micrometer) | 6 | Real study — not needed to pass. |
Bucket ① alone is 4 against a gap of 5, and buckets ①+③ are 7. Neither requires learning anything new. The single highest-value action available to you is to drill what you have already been taught.
| Section | #8 | #9 | #10 | #11 | #12 · 30 Aug | Verdict |
|---|---|---|---|---|---|---|
| Spring MVC | 40% | 57% | 50% | 100% | 100% | Finished — held twice |
| Testing | 100% | 100% | 33% | 60% | 75% | Recovering |
| Spring Core | 61% | 75% | 73% | 58% | 70% | Still never above 75% |
| Data Management | 75% | 81% | 82% | 86% | 70% | Slipped — needs a re-drill |
| Spring Security | 100% | 75% | 33% | 50% | 67% | Small section, 1 miss |
| Spring Boot | 71% | 56% | 86% | 75% | 61% | Decaying since its clinic |
| Overall | 70% | 73% | 70% | 70% | 68% | Flat — but see below |
REQUIRED.@DataJpaTest
loads @Service".IMMEDIATE, which is valid. The eighth of the series.@Property, Q16 @Configuration(enableTransactions=true)
and @EnableDatabaseTransactions). Holding at a low level.| Q | Topic | What you answered | The answer |
|---|---|---|---|
| Q1 | Resetting a logger level | "OPTIONS with null" + "not possible" | POST {"configuredLevel": null} |
| Q3 | Injecting a private field | "@Property" — invented | @Autowired · @Inject · @Value |
| Q4 | Default protocol for loggers | "SMTP" + "FTP" | JMX |
| Q6 | Lifecycle callback interfaces | "InitializingBean has postConstruct" | afterPropertiesSet() · destroy() |
| Q11 | Condition on a property value | "@ConditionalOnBean" | @ConditionalOnProperty · @ConditionalOnExpression |
| Q13 | Microservice advantages | "imply a simple distributed system" | Simpler codebase · independent deploy · fine-grained scaling |
| Q16 | Enabling @Transactional | Two invented options | @EnableTransactionManagement · or nothing, under Boot |
| Q17 | @Transactional attributes | "exit code" + "callback" | readOnly · rollbackFor · noRollbackFor · timeout |
| Q19 | Advantages of interfaces | "promotes high coupling" | The other five |
| Q24 | Invalid SpEL compiler mode | "IMMEDIATE" — which is valid | ON and LAZY don't exist |
| Q28 | Valid SpEL expressions | JavaScript arrow function — third time | T() · ![] · ?[] · method calls |
| Q29 | Why starters are useful | "reduce JAR size" + "optimize startup" | Curated versions · less research · auto-config |
| Q31 | @DataJpaTest | "It loads @Component/@Service/@Controller" | Embedded DB · scans repositories · transactional |
| Q37 | "Supports a current tx, creates one if none" | Four wrong options ticked | REQUIRED |
| Q39 | Advice types taking a JoinPoint | "None of the above" — also #6 | All four of them |
| Q45 | Authentication mechanisms | "LDAP" — also #11, yesterday | X.509 · Digest · Basic · OAuth2 · Form |
| Q50 | Actuator + monitoring systems | "custom @RestControllers" + "custom SPIs" | Micrometer |
| Q52 | Pointcut expression behaviour | Under-selected — three of four correct | Matches overloads · save* works · package-sensitive |
| Q53 | Web-only endpoints | "threaddump" — Boot clinic names this trap | heapdump · logfile |
61%, decaying since the clinic — and six of these seven are in it. This section needs a re-drill, not new material.
{"configuredLevel": null} to /actuator/loggers/{name}Every logger has two levels, and that's the whole question:
| Field | Means |
|---|---|
configuredLevel | the explicit override — may be null |
effectiveLevel | what actually applies, inherited from the parent if no override |
Sending null clears the override, so the logger falls back to inheritance.
Posting "INFO" instead would set a new override that happens to be INFO — not the same
thing, since the original might have been WARN inherited from a parent.
{"configuredLevel": null} = remove the override, restore inheritance. Setting INFO is not resetting.health is exposed by defaultThe enabled/exposed table, one more time:
| Transport | Default exposure |
|---|---|
| JMX | * — every enabled endpoint |
| HTTP | health only |
So loggers is reachable out of the box — over JMX. For HTTP you need
management.endpoints.web.exposure.include=loggers. And the two-transport rule kills SMTP and FTP
instantly: every Actuator property path contains either web or jmx.
heapdump and logfile — threaddump is not web-onlythreaddump.
The Boot clinic calls it out by name: "threaddump is the trap: it returns JSON,
so JMX handles it fine."The reason, so it sticks without memorising a list: JMX carries structured
attributes. heapdump returns a binary hprof file; logfile returns a
text stream. Neither fits down a JMX pipe. threaddump returns JSON — perfectly
carryable.
Ask "is it a file or a stream?" If yes, web-only. If it's structured data, both transports.
Only heapdump, logfile and jolokia qualify.
@ConditionalOnProperty — and @ConditionalOnExpression also works@ConditionalOnBean.
That tests for a bean, not a property value.| Annotation | Fires when |
|---|---|
@ConditionalOnProperty(name=…, havingValue="true") | the property has that value — the direct tool |
@ConditionalOnExpression("${…:false}") | a SpEL expression is true — more flexible |
@ConditionalOnBean / @ConditionalOnMissingBean | a bean is present / absent |
@ConditionalOnClass / @ConditionalOnMissingClass | a class is on the classpath |
@ConditionalOnResource | a resource exists on the classpath |
Read the suffix and it answers itself: OnProperty → a property,
OnBean → a bean, OnClass → a class, OnResource → a resource. The naming is
completely literal.
Useful detail: @ConditionalOnProperty has a matchIfMissing attribute —
true means "apply when the property is absent", which is how Boot writes opt-out defaults.
| Starters DO | Starters DON'T |
|---|---|
| Bundle a curated dependency set | Reduce jar size — they increase it |
| Align versions via the Boot BOM | Improve startup — more classes means slower |
| Pull in matching auto-configurations | Contain any code of their own (a starter is just a POM) |
| Save you researching compatible libraries | Include a database driver |
A starter is a convenience for the developer, not the runtime. If an option claims a starter improves a runtime characteristic — size, speed, memory — it's wrong. The benefit is entirely at build and configuration time.
Micrometer is "SLF4J for metrics" — one API, many backends. Add a registry dependency and it wires itself up:
| Add this artifact | And you get |
|---|---|
micrometer-registry-prometheus | /actuator/prometheus in Prometheus format |
micrometer-registry-datadog | metrics pushed to Datadog |
micrometer-registry-elastic, -influx, -statsd, -new-relic… | the same, per backend |
A CompositeMeterRegistry fans every meter out to all registered backends, so one
Counter can reach several systems at once. The integration is dependency-driven — you add a jar,
not code.
Don't confuse the two Actuator concerns: HealthIndicator feeds
/actuator/health (is the app alive?); Micrometer feeds metrics backends (how is it
behaving?). The third distractor tried to swap them.
micrometer-registry-* jar, write nothing. HealthIndicators are for /health, not for monitoring backends.| You gain | You pay |
|---|---|
| A small, comprehensible codebase per service | Network latency and partial failure |
| Independent deployment | Service discovery, versioning, contracts |
| Fine-grained scaling — scale only what's hot | Distributed transactions and eventual consistency |
| Technology choice per service | Far more operational surface |
The phrase "simple distributed system" is nearly a contradiction, and that's the tell. Distribution is the cost you accept in exchange for the three benefits — no architecture question will ever list it as an advantage.
(() => 2 * 3)() is JavaScript. It has never been valid in anything Java.Stop treating this as a SpEL question. It is a which language is this? question, and it can be answered without knowing SpEL at all:
| Syntax | Language |
|---|---|
() => expr — arrow function | JavaScript |
x -> expr — lambda | Java (and not valid in SpEL either) |
T(java.util.Date) | SpEL type reference |
list.![expr] | SpEL projection — map |
list.?[pred] | SpEL selection — filter |
isMember('x') | SpEL method invocation on the root object |
SpEL has no lambdas, no arrow functions and no anonymous functions of any kind. It is an expression language for navigating and filtering data, not a programming language. If a snippet defines and invokes a function, it isn't SpEL.
The four real ones are worth reciting as a set: T() reaches statics ·
![] maps · ?[] filters · a bare name is a method call on the root object.
=> is JavaScript. SpEL: T() statics, ![] map, ?[] filter, ^[]/$[] first/last. No functions, ever.ON and LAZY don't exist — IMMEDIATE doesIMMEDIATE, which is one of the three real modes.
The stem asked for the invalid ones.SpelCompilerMode | Compiles | On failure |
|---|---|---|
OFF (default) | never — always interpreted | — |
IMMEDIATE | as soon as possible, after the first evaluation | throws |
MIXED | after several interpreted runs | falls back silently |
ONLAZY | do not exist | |
Three modes, and the names describe when: never / immediately / after a
while. ON would be redundant with IMMEDIATE, and LAZY describes
when in a way the enum already covers with MIXED. Plausible words that duplicate an
existing member are usually invented.
On the polarity slip — the eighth. Every one has followed the same shape: a capitalised or emphasised negative in the stem, answered in the positive. The mechanical fix costs five seconds: before reading the options, say what you are hunting for. "I want the ones that do NOT exist."
JoinPoint same wrong option as #6 Q22| Advice | First parameter |
|---|---|
@Before | JoinPoint — optional |
@After | JoinPoint — optional |
@AfterReturning | JoinPoint — optional, then the returning binding |
@AfterThrowing | JoinPoint — optional, then the throwing binding |
@Around | ProceedingJoinPoint — required |
"None of the above" is an absolute, and on this paper it was the only wrong option among five. Taking it means asserting that all four listed advice types reject a parameter Spring documents for every one of them.
The one fact behind it: ProceedingJoinPoint extends JoinPoint. So
@Around doesn't lack JoinPoint — it needs the richer subinterface, because only it can call
proceed().
Read execution(* com.example.service.UserService.saveUser(..)) segment by segment:
| Segment | Means |
|---|---|
* | any return type |
com.example.service.UserService | that one class — so moving it breaks the expression |
saveUser | exact method name — change to save* for a prefix |
(..) | any parameter list — so all overloads match |
| Expression | Matches |
|---|---|
…UserService.saveUser(..) | every saveUser overload in UserService |
…UserService.save*(..) | all save… methods in that class |
…service.*.saveUser(..) | saveUser in any class in the package |
com.example..*.saveUser(..) | …and in sub-packages too (..) |
Two dots mean two different things, which is the thing to hold:
(..) in the parameter position = any arguments; .. in the package position =
this package and below.
(..) = any parameters (so all overloads). .. in a package = and sub-packages. * = one segment.@Autowired, @Inject and @Value all work — via reflection@Property.
No such annotation exists in Spring. You were right about the other three.Private is not an obstacle: Spring uses
ReflectionUtils.makeAccessible() to bypass access control, so all three inject into a private field
exactly as they would a public one.
| Annotation | Origin | Injects |
|---|---|---|
@Autowired | Spring | a bean, by type |
@Inject | JSR-330 | a bean, by type |
@Resource | JSR-250 | a bean, by name |
@Value | Spring | a property or SpEL result |
@Property | — | does not exist |
Why @Property feels real: it's the plain-English word for what
@Value does — the same family as Language for Locale and
@NoRollback for @Rollback(false). Everyday words standing in for technical
ones.
Worth remembering the caveat the question notes: field injection works but is discouraged —
constructor injection makes dependencies explicit, supports final fields, and needs no reflection to
test.
InitializingBean.afterPropertiesSet() and DisposableBean.destroy()afterPropertiesSet(); @PostConstruct is a separate
JSR-250 annotation.| Route | Init | Destroy |
|---|---|---|
| Annotation (JSR-250) | @PostConstruct | @PreDestroy |
| Interface (Spring) | InitializingBean.afterPropertiesSet() | DisposableBean.destroy() |
| Attribute | @Bean(initMethod = "…") | @Bean(destroyMethod = "…") |
Three routes, and they run in that order — annotation, then interface, then attribute — on both sides of the lifecycle. That ordering has appeared on several of your papers.
The names are worth reading literally: afterPropertiesSet says
when it runs — after dependency injection completes. @PostConstruct says the same thing in
a different vocabulary. Same moment, two mechanisms, two different names — and neither borrows the other's.
There is no init() in InitializingBean either.
Prefer the annotations: the interfaces couple your domain classes to Spring, which is exactly what "non-invasive" is meant to avoid.
The five genuine advantages, and one is Spring-specific:
| Advantage | Why |
|---|---|
| Implementation hiding | the contract is separate from the how |
| Decoupling | depend on the type, not the class |
| Easy bean switching | swap a mock in tests, a strategy in production |
| Interchangeability | a direct consequence of the above |
| Enables JDK dynamic proxies | Spring AOP can proxy via the interface instead of subclassing |
The proxy point is the exam-relevant one. With an interface Spring can
use a JDK dynamic proxy; with no interface it must use CGLIB, which can't proxy final classes or
methods. Note that Spring Boot still defaults to CGLIB (proxyTargetClass=true) — having an
interface makes JDK proxying possible, not automatic.
And the pitfall that follows: under a JDK proxy, injecting the concrete class fails with
BeanNotOfRequiredTypeException — the proxy implements the interface but is not an instance of your
class. Inject the interface.
REQUIRED — and you ticked four other optionsNOT_SUPPORTED, MANDATORY, NEVER and
SUPPORTS. Four ticks on a question with one answer — and the stem is the
textbook definition of the default.Take the stem's two clauses and eliminate:
| Clause | Rules out |
|---|---|
| "supports a current transaction" (joins one) | NOT_SUPPORTED (suspends), NEVER (throws) |
| "creates a new one if none exists" | MANDATORY (throws), SUPPORTS (runs non-tx) |
| "joins or creates" | REQUIRES_NEW (always creates, never joins) |
| what's left | REQUIRED |
SUPPORTS is the near-miss, and the trap is the word "supports" in
the stem. SUPPORTS joins an existing transaction but runs without one when none exists — it
never creates. The second clause is what separates them.
The seven levels, in the two columns that answer every propagation question:
| Propagation | No existing tx | Existing tx |
|---|---|---|
REQUIRED (default) | create | join |
REQUIRES_NEW | create | suspend, create |
NESTED | create | savepoint |
SUPPORTS | run non-tx | join |
NOT_SUPPORTED | run non-tx | suspend |
MANDATORY | throw | join |
NEVER | run non-tx | throw |
@Transactional two invented options@EnableTransactionManagement — or nothing at all under Boot@Configuration(enableTransactions=true) and
@EnableDatabaseTransactions. Neither exists. Both correct options went
unticked.The naming convention would have eliminated both: Spring feature switches are always
@EnableXxx on a @Configuration class, never an attribute of
@Configuration itself.
| Real | Enables |
|---|---|
@EnableTransactionManagement | @Transactional |
@EnableAspectJAutoProxy | @Aspect |
@EnableCaching | @Cacheable |
@EnableScheduling · @EnableAsync | @Scheduled · @Async |
And the second correct option matters: in Spring Boot you write
nothing — TransactionAutoConfiguration applies
@EnableTransactionManagement for you when a PlatformTransactionManager is present.
"None of the above, if you use auto-configuration" was true.
@Transactional can configurereadOnly, rollbackFor, noRollbackFor, timeout| Attribute | Type | Default |
|---|---|---|
propagation | enum | REQUIRED |
isolation | enum | DEFAULT |
timeout | int seconds | -1 |
readOnly | boolean | false |
rollbackFor / rollbackForClassName | classes / strings | — |
noRollbackFor / noRollbackForClassName | classes / strings | — |
transactionManager (alias value) | bean name | the primary one |
Where the two fake ideas actually live, which makes them easier to reject:
exit codes are ExitCodeGenerator (a Boot concern); post-commit/post-rollback hooks are
TransactionSynchronization, registered through
TransactionSynchronizationManager. Both are real capabilities — just not attributes of this
annotation.
And there is no retry attribute either — that's @Retryable from Spring Retry.
@DataJpaTest@Service or @Controller| Loaded | Not loaded |
|---|---|
@Entity classes | @Component, @Service, @Controller |
| Spring Data repositories | the web layer |
TestEntityManager | full auto-configuration |
DataSource + JpaTransactionManager |
Two defaults it applies, both of which appear as their own questions:
an embedded in-memory database replaces your configured one (disable with
@AutoConfigureTestDatabase(replace = Replace.NONE)), and each test runs in a transaction that is
rolled back afterwards.
The mirror of @WebMvcTest from yesterday's paper: each slice loads exactly one
layer and mocks or omits the rest. If a slice question offers "it also loads services", that's the wrong
answer by definition.
| Layer | Answers | Members |
|---|---|---|
| Mechanism | how do credentials arrive? | Basic · Digest · Form · X.509 · OAuth2/OIDC |
| User store | where do the users live? | LDAP · JDBC · in-memory |
The reason this keeps catching you is that LDAP is the right answer to a neighbouring question. "Where can Spring Security store user details?" → JDBC, LDAP, in-memory. Same word, opposite verdict, depending on which layer the stem asks about.
So read the stem's noun. "Mechanism" / "how credentials are collected" ⇒ LDAP is out. "Store" / "where user details are kept" ⇒ LDAP is in.
In practice they combine: a Form login collects the credentials, and
LdapAuthenticationProvider verifies them against the LDAP directory.
{"configuredLevel": null} — clears the override, restores inheritance* by default; HTTP exposes health only. Just those twoheapdump, logfile (a file and a stream). threaddump is JSON — JMX carries it@ConditionalOnProperty or @ConditionalOnExpression; the suffix names what is testedmicrometer-registry-* jar and write nothingT() statics · ![] map · ?[] filter · method calls on the rootSpelCompilerMode: OFF · IMMEDIATE (throws) · MIXED (falls back). No ON, no LAZYJoinPoint; @Around requires ProceedingJoinPoint, which extends it(..) = any parameters (all overloads) · .. in a package = and sub-packages · * = one segment@Autowired, @Inject, @Resource, @Value. No @PropertyInitializingBean.afterPropertiesSet() · DisposableBean.destroy(). @PostConstruct is the JSR-250 annotationREQUIRED = join or create · SUPPORTS = join or run without. The difference is what happens with no transaction@EnableTransactionManagement in plain Spring; nothing under Boot auto-configurationFor, transactionManager. No callback, exit code or retry@DataJpaTest: JPA layer only, embedded DB, transactional and rolled back. Slices never load the layer above