37/60 = 62%. You used 1h 59m 36s — nearly triple attempt #12's 41 minutes — and still left two questions blank and scored 4 marks lower. That combination rules out the two explanations you'd reach for first: it wasn't rushing, and it wasn't the clock. 21 of the 23 misses are selection-count errors — ticking one option too many, or one too few — on questions where you knew the material. This page is built to fix that, and it carries 70 drills.
62% is your second-worst result in thirteen papers — only attempt #6's 58% is lower, and it ties
attempt #3. The gap to a pass is +9 questions, the widest since #6.
Every section is now clustered between 56% and 67%. That flatness is itself the finding: there is
no strong section left to lean on and no single catastrophic one to blame. Nothing is above 67%.
MVC fell 100% → 60%, the second consecutive paper confirming its clinic gain has fully decayed
(the mixed set already showed 57%). Testing fell 75% → 56% three days after its clinic was written.
Every review on this site has told you to slow down. You did — emphatically. You used 92% of the available time, leaving 10 minutes instead of 89. And the score went down four marks.
Two questions were still left blank (Q11 and Q12) — with two hours on the clock. Both were single-answer questions you would answer correctly right now:
| Q | Question | Answer |
|---|---|---|
| Q11 | What is Dependency Injection? | "DI is a Pattern" — Spring is the framework that implements it |
| Q12 | REST is ______, and usually uses the ______ protocol | stateless, HTTP — REST is an architectural style, not a protocol |
Two free marks abandoned — 2 of the 9 you need. On a scored multiple-choice paper with no negative marking, a blank is strictly worse than a guess. There is never a reason to submit an unanswered question.
What the extra 79 minutes actually bought. Look at the failure shape below: 14 over-selections. Extra time spent staring at four plausible options does not produce certainty — it produces doubt, and doubt gets resolved by ticking one more box "to be safe." Longer deliberation is exactly the mechanism that generates over-selection. The fix is not more time or less time; it is a decision rule applied at constant speed.
This is the most important table on the page. It splits the 23 misses by how they were lost rather than by topic.
| Failure mode | Marks | What happened |
|---|---|---|
| Over-selection | 14 | Every correct option ticked plus one wrong extra. Q3, Q13, Q17, Q21, Q24, Q25, Q26, Q31, Q36, Q42, Q44, Q51, Q56, Q60 |
| Under-selection | 7 | Every tick right, but stopped before the last true option. Q29, Q35, Q37, Q49, Q53, Q54, Q58 |
| Left blank | 2 | Q11, Q12 — no answer submitted, with 10 minutes spare |
| Genuine knowledge gaps | 0 | There is no question on this paper where you ticked only wrong options |
Read that last row again. On all 21 answered misses you identified correct material. Not one question was a blank-slate topic. You are losing this exam on arithmetic, not on Spring.
The single rule that recovers most of it: rule every option true or false on its own merits, write the verdict down, then tick exactly the true ones. Never tick an option because it "might be included"; never stop ticking because "three feels like enough." An option you cannot rule true is false.
Note that over-selection and under-selection are opposite errors appearing on the same paper — which means neither is a habit you can correct by leaning one way. Only the per-option verdict works.
Previous highs were four (#12) and three (mixed set). These are not merely similar questions; each has a page here that names the exact distinction being tested.
| Q | Topic | Where it already lives | Sitting |
|---|---|---|---|
| Q53 | Advice types that accept a JoinPoint | #6 Q22 · #12 Q39 — taught in review #5 | 4th |
| Q31 | Micrometer meter primitives | Boot clinic §③ — already marked "three times" | 4th |
| Q25 | @SpringBootTest + @ExtendWith on JUnit 5 | #6 Q28 · Testing clinic | 3rd |
| Q21 | permitAll() vs web.ignoring() | Security clinic — has this exact drill | 3rd |
| Q49 | @WebMvcTest includes @JsonComponent | Testing clinic — the list is spelled out | 3rd |
| Q13 · Q24 | Self-invocation bypasses the proxy | Data clinic · lesson 9 | 3rd |
| Q35 | Boot as a WAR on Tomcat / Jetty | Boot clinic · MVC round 3 | 2nd |
Q53 is the one to sit with. "All four non-around
advice types take an optional JoinPoint" is written verbatim in the closing list of
review #12, eight days ago. The good news is real and worth naming:
you no longer tick "None of the above" — the trap that cost this question in #6 and #12. You have moved the
error from wrong to incomplete. One more step: tick all four.
| Bucket | Marks | What it costs you |
|---|---|---|
| Answer every question | 2 | Zero study. Never leave a box blank — Q11 and Q12 were both easy |
| AOP & proxy mechanics | 5 | Q13, Q24, Q51, Q53, Q60 — one topic, four tables on this page |
| Transaction plumbing | 3 | Q17, Q44, Q58 — the enablement triad and the public rule |
| Re-drill the seven repeats | 7 | Material you already own; 20 minutes of drilling, not reading |
| Rule every option T/F | 21 | The ceiling if selection discipline were perfect — 62% → 100% |
These overlap deliberately. You need +9. Answering the two blanks and fixing AOP alone is +7. Add the transaction triad and you are at +10 — a pass, without touching MVC, Testing, Boot or Security.
| Q | Topic | What went wrong | The answer |
|---|---|---|---|
| Q3 | application.yml effects | Ticked "a DataSource bean of type OracleDataSource" | Boot auto-configures a DataSource (Hikari) from the properties |
| Q11 | What is Dependency Injection? | Left blank | DI is a pattern; Spring is a framework implementing it |
| Q12 | REST is ___, uses the ___ protocol | Left blank | stateless, HTTP |
| Q13 | Which AOP mechanism supports self-invocation | Also ticked "JDK Dynamic Proxy" | AspectJ only — weaving, not proxying |
| Q17 | Which two are required for transactions | Ticked three — added "write an AOP advice" | @Transactional + @EnableTransactionManagement |
| Q21 | Bypass the filter chain entirely | Also ticked .ignoreAll() — invented method | web.ignoring().antMatchers("/public/**") |
| Q24 | Limitations in both proxy types | Ticked a CGLIB-only limitation (final methods/classes) | Self-invocation · private methods |
| Q25 | @SpringBootTest statements | Ticked "JUnit 5 must also add @ExtendWith" | JUnit 4 needs @RunWith; JUnit 5 needs nothing |
| Q26 | getBean("myService") | Ticked "returns null if not found" | Throws NoSuchBeanDefinitionException |
| Q29 | True about MVC controllers | Under-selected — all four were true | POJO · no Servlet API · stereotype · testable |
| Q31 | Micrometer primitives | Ticked "Memory Usage" / "CPU Usage" | Counter · Gauge · Timer |
| Q35 | How to execute a Boot application | Under-selected — missed a WAR option | java -jar · WAR on Tomcat · WAR on Jetty |
| Q36 | @Autowired statements | Ticked "reflection for fields but not constructors/setters" | Reflection is used for all three |
| Q37 | @Value statements | Under-selected — three of four true | Method args · SpEL · Maps |
| Q42 | @MockBean in an integration test | Ticked "the test still uses the real bean" | @MockBean replaces the bean in the context |
| Q44 | @Transactional method visibility | Also ticked "Any visibility level" | public only |
| Q49 | What @WebMvcTest picks up | Under-selected — missed @JsonComponent | @Controller · @RestController · @ControllerAdvice · @JsonComponent |
| Q51 | Statements about AOP advices | Ticked "proceed() at most once" | proceed() may be called many times |
| Q53 | Advice types supporting JoinPoint | Under-selected — all four were true | @Before · @After · @AfterReturning · @AfterThrowing |
| Q54 | Auto-provided controller arguments | Under-selected — all four were true | Errors · Model · ServletRequest · ServletResponse |
| Q56 | Running SQL scripts before tests | Ticked SqlRunner / @SqlScript — both invented | @Sql · ResourceDatabasePopulator.addScripts() |
| Q58 | Enabling declarative transactions | Under-selected — three of four true | Manager bean · @Transactional · @EnableTransactionManagement |
| Q60 | When is code modified for LTW | Also ticked "at runtime by JDK/CGLIB proxies" | When classes are loaded by the classloader |
Five of the twenty-three misses are one topic, and they sit on one axis: when and how the advice gets attached to your code. Get this table and Q13, Q24 and Q60 all fall at once.
| Strategy | When | Mechanism | Self-invocation advised? |
|---|---|---|---|
| Compile-time weaving | ajc compiles | AspectJ compiler rewrites the .class | Yes |
| Load-time weaving (LTW) | ClassLoader loads the class | Java agent + ClassFileTransformer | Yes |
| Runtime weaving (Spring AOP default) | Bean instantiation | JDK / CGLIB proxy wrapper | No |
The one sentence that answers both questions: a proxy wraps the object; weaving
changes the class. A wrapper can only see calls that arrive from outside, so this.inner() never
reaches it. Woven bytecode is the object, so there is nothing to bypass.
Q60's exact wording matters. "Code is modified" — JDK and CGLIB proxies
never modify your code; they generate a separate class that delegates to it. Your original
.class file is byte-for-byte untouched. That option is disqualified by the verb in the stem.
LTW needs -javaagent:spring-instrument.jar plus
@EnableLoadTimeWeaving; for transactions specifically,
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ) plus the spring-aspects
dependency.
final option. Both correct answers
(self-invocation, private) were also ticked — the mark went to the extra.This question is answerable purely from how each proxy is built, without memorising a list. JDK proxies implement an interface. CGLIB proxies extend the class. Every limitation follows:
| Limitation | JDK (implements) | CGLIB (extends) | Both? |
|---|---|---|---|
| Self-invocation not advised | yes | yes | BOTH |
private methods not advised | yes | yes | BOTH |
final methods not advised | n/a — interface | yes | CGLIB only |
final classes not proxied | n/a — interface | yes | CGLIB only |
Why final is CGLIB-only: CGLIB works by subclassing and
overriding. Java forbids overriding a final method and forbids extending a final
class, so CGLIB is blocked. A JDK proxy never subclasses anything — it implements the interface — so
final on the target class is irrelevant to it.
Why private is both: a private method is not visible to an interface
or to a subclass. Neither strategy can see it. Spring AOP advises public method execution join points
only.
final only blocks the one that extends. private and this.method() block both.JoinPoint under-selected 4th sitting| Advice | First parameter | Controls execution? |
|---|---|---|
@Before | JoinPoint — optional | No |
@After | JoinPoint — optional | No |
@AfterReturning | JoinPoint — optional, then the returning binding | No |
@AfterThrowing | JoinPoint — optional, then the throwing binding | No |
@Around | ProceedingJoinPoint — required | Yes — must call proceed() |
The whole question is one inheritance fact:
ProceedingJoinPoint extends JoinPoint. So every advice type accepts a JoinPoint —
@Around just needs the richer subinterface, because only it can call proceed(). There is
no advice type that rejects introspection.
When the stem lists only the four non-around types, the answer is all of them. That is worth holding as a shape: a list of the four "after/before" advices with "None of the above" appended is a question where every named option is true.
proceed() may be called any number of times — including zeroproceed() must be called up to 1 time only".
You correctly rejected the invented @AfterFinally, and correctly took both true
options.ProceedingJoinPoint.proceed() is an ordinary method call. Calling it twice runs
the target twice — that is exactly how retry advice is written. Calling it zero times means the target
never runs, which is how caching and short-circuit authorisation advice work.
@Around("execution(* com.app..*(..))")
public Object retry(ProceedingJoinPoint pjp) throws Throwable {
for (int i = 0; i < 3; i++) {
try { return pjp.proceed(); } // called up to THREE times
catch (TransientException e) { /* retry */ }
}
throw new IllegalStateException("gave up");
}
The other three options, ruled individually:
| Option | Verdict | Why |
|---|---|---|
proceed() at most once | FALSE | Zero, one or many — retry and caching depend on it |
@AfterFinally runs either way | FALSE | No such annotation. The finally-semantics advice is @After |
First param of @Around must be ProceedingJoinPoint | TRUE | Required, and must be first |
@AfterThrowing(throwing=...) narrows the match by exception type | TRUE | The declared parameter type filters which throws are advised |
Spot the shape: the false option contains the absolute "must ... only", and the other contains an invented name. Both are recognisable without knowing AOP. Absolutes and invented names are your two most reliable eliminators — they have accounted for a miss on every paper in this series.
proceed(): 0, 1 or many. @After is the finally-advice — there is no @AfterFinally.Three components, and the stem decides how many are on offer:
| # | Component | Provides | If missing |
|---|---|---|---|
| 1 | @EnableTransactionManagement on a @Configuration class | The infrastructure — TransactionInterceptor + auto-proxy creator | @Transactional is silently ignored |
| 2 | A PlatformTransactionManager @Bean | The resource manager — JPA, JDBC or JTA | Fails at runtime when a transactional method is called |
| 3 | @Transactional on the class or method | The declaration of intent | Nothing is demarcated — no error, no transaction |
Why Q17 says "two" and Q58 has "three". Q17's option list did not offer the manager bean, so only components 1 and 3 were available. Q58's did. The count in the stem is a fact about the option list, not about Spring. Rule each option on its own merits and both questions answer themselves — you never need to make the number come out.
The distractor you took on Q17: "write a Spring AOP advice to implement
transactional behaviour." Spring's transaction support is built on AOP — that is why it feels right — but
@EnableTransactionManagement registers TransactionInterceptor for you. The word
that disqualifies the option is "write". Likewise, you never implement
TransactionInterceptor yourself.
Under Spring Boot: TransactionAutoConfiguration supplies
component 1, and the JDBC/JPA starters auto-configure component 2. You still write
@Transactional yourself — Boot cannot guess which methods should be transactional.
@Transactional and method visibility over-selectedpublic — and "Any visibility level" is the absolute that gives it awaypublic
would be pointless.Use the contradiction. When an option list contains a specific answer and an "any / all / none" catch-all, they cannot both be true. Ticking both guarantees a zero. Pick one.
| Visibility | Proxy-based @Transactional | Why |
|---|---|---|
public | Applied | Visible through the interface and to a subclass |
protected | Ignored | CGLIB could override it, but Spring's tx infrastructure does not process it |
| package-private | Ignored | Not part of the proxied contract |
private | Ignored | Cannot be overridden or intercepted by anything |
The dangerous part, which the exam likes: Spring gives you no warning.
@Transactional on a non-public method compiles, starts and runs — with no transaction at all. Same
silent failure mode as self-invocation, and for the same reason.
The escape hatch is the same one as Q13: switch to AspectJ weaving
(@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)), which can advise non-public methods
because there is no proxy — the target class itself is woven. Two conditions apply, and both are worth
knowing precisely:
Where @Transactional sits | AspectJ pointcut | Advises private? |
|---|---|---|
| On the method | execution(@Transactional * *(..)) | Yes — no visibility modifier in the expression |
| On the class | execution(public * ((@Transactional *)+).*(..)) | No — public is written into the pointcut |
So annotating the non-public method directly is the only way to demarcate it — a class-level
@Transactional will not reach it even under AspectJ. The mechanism is one constructor argument:
AnnotationTransactionAspect builds its attribute source with
new AnnotationTransactionAttributeSource(false) — publicMethodsOnly = false —
where proxy mode uses the no-arg constructor and gets true.
And the mode attribute alone does nothing.
AdviceMode.ASPECTJ needs spring-aspects on the classpath plus real weaving —
the LTW agent (-javaagent:spring-instrument.jar) or compile-time weaving with ajc.
Set the mode without weaving and no aspect is applied at all: you silently lose transactions everywhere,
not just on the private method.
Version note for your exam. This site targets Spring 5.3, where proxy mode
is public-only — so this question's answer is public. As of Spring 6.0,
protected and package-visible methods are advised by class-based (CGLIB) proxies by
default; interface-based proxies still require public. private is never advised by
any proxy, in any version.
public only, and silently. AspectJ reaches private — but only when the annotation is on the method, and only if weaving is actually configured.The three wrong options are all category errors, and each is worth recognising by shape:
| Option | Verdict | What it actually describes |
|---|---|---|
| DI is a Pattern | TRUE | A design pattern: dependencies supplied from outside |
| DI is a Framework | FALSE | Spring is the framework. DI is framework-independent |
| Automatic Dependency Resolution for Java | FALSE | Maven / Gradle resolving artifacts — build time, not runtime |
| Automatic Package Resolution for Java | FALSE | How the JVM resolves class names within packages |
The related distinction the exam also asks: IoC is the broader principle ("don't call us, we'll call you" — the framework calls your code). DI is one specific form of IoC: the container supplies collaborators via constructor, setter or field. Spring implements both. Do not treat DI and IoC as synonyms in an exam question.
context.getBean("myService") over-selectednullnull if the bean is not found".
You took both true options and correctly rejected "the bean is retrieved by type".Spring's container is fail-fast by design. Returning null would push
the failure to a later NullPointerException far from the cause. Every lookup failure has its own
exception:
| Situation | Result |
|---|---|
| Bean name not defined | NoSuchBeanDefinitionException |
| Lookup by type, several candidates | NoUniqueBeanDefinitionException |
| Found, but not the requested type | BeanNotOfRequiredTypeException |
| Bean found | The instance — never null |
The three overloads, and which one the snippet used:
Object b = context.getBean("myService"); // by NAME → needs a cast
MyService s = context.getBean("myService", MyService.class); // by NAME + type → type-safe
MyService s = context.getBean(MyService.class); // by TYPE → throws if ambiguous
The snippet uses form 1, so "the bean is retrieved by type" is false — the string
"myService" is a name. And getBean() is overloaded to return a specific
type, which makes that option true.
@Autowired over-selectedThink about what the container has to do. It holds a Class object and
must produce an instance. There is no compiled call site anywhere — Constructor.newInstance(),
Method.invoke() and Field.set() are all reflection. Field injection is not special;
it just additionally needs ReflectionUtils.makeAccessible() to bypass private.
| Statement | Verdict | Why |
|---|---|---|
| Reflection for fields but not constructors/setters | FALSE | All three use reflection |
Often interchangeable with @Inject | TRUE | JSR-330's @Inject is handled by the same post-processor |
| It is a JSR-330 annotation | FALSE | @Inject is JSR-330. @Autowired is Spring's own |
| The dependency must be satisfied by default | TRUE | required = true; relax with required = false or Optional<T> |
The @Autowired / @Inject pair is worth pinning down
because two of the four options turn on it. @Autowired is Spring, in
org.springframework.beans.factory.annotation, and has a required attribute.
@Inject is the JSR-330 standard, in javax.inject, and has no attributes at all
— which is precisely why it cannot express required = false.
required. @Inject = JSR-330, no attributes. Reflection drives constructor, setter AND field.@Value under-selected@Value does more than you gave it credit for@Value resolves two syntaxes, and the difference is the most testable fact
about it:
| Syntax | Name | Resolved by | Example |
|---|---|---|---|
${...} | Property placeholder | The Environment | @Value("${app.name}") |
#{...} | SpEL expression | The expression parser | @Value("#{systemProperties['os.name']}") |
Where it can be placed: fields, setter parameters, constructor parameters and
@Bean method parameters — that last one is the "method arguments" option you left untaken.
And #{${app.map}} — a placeholder nested inside a SpEL expression — injects a
Map, which is the "inject into Maps" option.
@Bean
DataSource ds(@Value("${db.url}") String url) { ... } // method ARGUMENT — true
@Value("#{${app.map}}") Map<String,String> config; // into a MAP — true
@Value("${app.servers}") List<String> servers; // comma-separated → List
The single false option — validation — is a layer error. Injection puts a value
in; validation checks a value that is already there. Constraints come from Bean Validation
(@NotNull, @Size, @Min) plus @Validated.
Ask "does this option describe a different Spring feature?" — that is the shape of most false options in
this bank.
${} = property · #{} = SpEL. Fields, setters, constructor args AND @Bean method args. It injects; it never validates.MVC has now fallen from 100% to 60% across two papers (the mixed set showed 57%), returning to almost exactly its pre-clinic level of 50%. The three clinic rounds worked — and then wore off, because they were never re-drilled. Note what the three misses are: one blank and two under-selections. None is an MVC knowledge gap.
Eliminate on the second blank first. The stem says "uses the ___ protocol". REST is an architectural style, so any option naming REST as the protocol dies immediately — that removes two of four without thinking about statefulness at all.
| Property | Value |
|---|---|
| Type | Architectural style — not a protocol, not a framework |
| State | Stateless — the server keeps no client session between requests |
| Protocol | Typically HTTP |
| Resources | Identified by URIs |
| Operations | HTTP methods: GET, POST, PUT, DELETE, PATCH |
"Stateless" precisely: every request carries everything needed to serve it. It does not mean the application has no state — the database is full of it. It means no client session state on the server between requests.
All four statements describe the same single design decision — annotation-based controllers are plain objects — from four angles:
| Statement | Why it is true |
|---|---|
| No base class or interface required | Handler mapping is driven by annotations, not by type |
| No direct Servlet or Portlet API dependency | DispatcherServlet adapts request → method arguments |
@Controller is a stereotype | A @Component meta-annotation, found by component scanning |
| They are testable artifacts | Follows from the first two — instantiate with new, or use MockMvc |
"All options correct" is a documented, recurring pattern in this bank — the MVC round 3 page already counted seven of them, and this paper adds two more on its own (Q29 and Q54). You have now lost more marks to this one question shape than to any single Spring topic.
The counting instinct is what fails here. "Four ticks feels like too many" is not evidence. When every option restates one idea from a different angle — as all four do here — that redundancy is the signal that they are all true, not a reason to choose between them.
Errors, Model, ServletRequest, ServletResponseThe supported-argument list is long, and the exam draws from it repeatedly. Worth holding whole:
| Type | Gives you |
|---|---|
Model / ModelMap / ModelAndView | Attributes exposed to the view |
Errors / BindingResult | Validation and binding errors |
ServletRequest / HttpServletRequest | The raw request |
ServletResponse / HttpServletResponse | The raw response |
HttpSession | The session (creates one if absent) |
Principal | The authenticated user |
Locale, TimeZone | Resolved by the LocaleResolver |
InputStream / Reader, OutputStream / Writer | Raw body access |
HttpEntity<T>, UriComponentsBuilder, SessionStatus | Body + headers, URI building, session completion |
The one positional rule the exam tests:
Errors/BindingResult must be declared immediately after the validated
@ModelAttribute or @RequestBody argument. Put anything between them and Spring cannot
associate the two — you get a BindException (a 400) instead of your error-handling branch.
@PostMapping("/users")
String create(@Valid @ModelAttribute UserForm form,
Errors errors, // MUST be immediately after the validated arg
Model model,
HttpServletRequest req) { ... }
Errors goes directly after the object it validates.The Testing clinic was written five days ago and Testing is now your worst section. Two of these four misses are covered on that page by name. It confirms the pattern this site keeps documenting: a clinic that is read once decays within a week; the ones that hold are the ones that get re-drilled.
@SpringBootTest and JUnit versions over-selected 3rd sitting@RunWith. JUnit 5 needs nothing.@ExtendWith(SpringExtension.class)". You also ticked the option stating the
exact opposite — that @ExtendWith is contained in @SpringBootTest. Those two
cannot both be true.Use the contradiction, as on Q44. "You must add X" and "X is already included" are mutually exclusive. Whenever an option list contains a statement and its negation, exactly one is true — ticking both is a guaranteed zero. That pattern appeared twice on this paper.
| JUnit | Extra annotation needed? | Why |
|---|---|---|
| JUnit 4 | Yes — @RunWith(SpringRunner.class) | JUnit 4 runners are not composable, so it must be declared |
| JUnit 5 | No — nothing | @SpringBootTest is meta-annotated @ExtendWith(SpringExtension.class) |
This generalises to every slice. @WebMvcTest,
@DataJpaTest, @JsonTest, @WebFluxTest — every Spring Boot test slice
carries @ExtendWith(SpringExtension.class). Adding it yourself is redundant, never required.
Note that "redundant" is not "wrong": adding @ExtendWith explicitly compiles and runs
fine. But the option said "must" — and that is the word that makes it false.
@RunWith(SpringRunner.class). JUnit 5 → nothing; every slice already carries SpringExtension.@MockBean in a @SpringBootTest over-selected@MockBean replaces the bean in the context — that is its entire purposeOrderService bean despite
@MockBean". You took all three true options; the mark went to the extra
tick.Rule that option against the code in the stem. The test stubs
when(orderService.getOrderById(1L)).thenReturn("Mock Order Details") and then asserts the response
body is "Mock Order Details". If the real bean were used, the assertion could not
pass — the option contradicts the snippet it is asked about.
| Annotation | Origin | Effect on the Spring context |
|---|---|---|
@Mock | Mockito | None — plain unit tests; Spring never sees it |
@MockBean | Spring Boot test | Replaces the real bean with a mock |
@SpyBean | Spring Boot test | Wraps the real bean — real methods unless stubbed |
The performance fact the exam also asks: a @MockBean changes the
context definition, so it produces a new, separately cached application context. Scatter different
@MockBean combinations across many test classes and you rebuild the context repeatedly — the usual
cause of a slow Boot test suite.
And @MockBean works on any Spring-managed bean — services, repositories,
components, even controllers. The option restricting it to services was correctly rejected.
@WebMvcTest picks up under-selected 3rd sitting@JsonComponent is the one you keep leaving behind@Repository and @Service. The list is spelled out in the
Testing clinic, which has a drill asking exactly this.One question decides everything: does this bean take part in handling an HTTP
request? @JsonComponent registers Jackson serializers, which run during HTTP message
conversion — so it is in. A @Service is business logic behind the web layer — so it is out.
| Loaded by the slice | Excluded — mock it |
|---|---|
@Controller, @RestController | @Service |
@ControllerAdvice | @Repository, JPA, DataSource |
@JsonComponent | Plain @Component |
Converter / GenericConverter | TestRestTemplate (needs a real port) |
Filter, HandlerInterceptor | |
WebMvcConfigurer, HandlerMethodArgumentResolver | |
MockMvc (auto-configured) |
Two things worth knowing beyond the list: Spring Security auto-configuration is
on inside the slice, so endpoints can return 401 — use @WithMockUser. And
@WebMvcTest(OrderController.class) narrows to that one controller, but all
@ControllerAdvice is still loaded.
SqlRunner.runScript() and/or @SqlScript.
Neither exists in Spring. This is the failure mode the Testing clinic had driven to
zero — it is back.| Option | Real? | What it actually is |
|---|---|---|
@Sql | REAL | Declarative — on a test class or method, with executionPhase |
ResourceDatabasePopulator.addScripts() | REAL | Programmatic — register scripts, then execute(dataSource) |
| INVENTED | The plain-English expansion of @Sql |
| INVENTED | A plausible-sounding class that does not exist |
The tell is the same one as @Property, @NoRollback and
@AfterFinally: the fake is the everyday-English version of the real name.
@Sql runs SQL scripts, so @SqlScript sounds more descriptive — and that extra
descriptiveness is exactly the signal. Spring's real names are terse: @Sql, @Rollback,
@Commit, @Value.
And "Runner" is a loaded word in this exam. The real Spring runners are
SpringRunner, SpringJUnit4ClassRunner and CommandLineRunner. There is no
SqlRunner. When you see an unfamiliar XxxRunner or XxxTemplate, check it
against the ones you can actually name.
@Test
@Sql(scripts = "classpath:data.sql",
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // the default
void loadsSeedData() { ... }
@Sql declarative · ResourceDatabasePopulator programmatic. No @SqlScript, no SqlRunner — the wordier name is the fake one.application.yml actually does over-selectedDataSource — it does not declare an OracleDataSourceDataSource bean is declared of type
OracleDataSource". Both true options were taken, and you correctly rejected the
Jetty distractor.The distinction is configured vs declared as a type. Boot's
DataSourceAutoConfiguration picks the connection-pool implementation from the classpath —
HikariDataSource by default in Boot 2.x, then Tomcat JDBC, then Commons DBCP2. Your
driver-class-name tells that pool which JDBC driver to load; it does not change the bean's
type.
| Property | Effect |
|---|---|
server.port: 8081 | Embedded Tomcat (Boot's default) listens on 8081 |
server.servlet.context-path: /boot | App is served at http://localhost:8081/boot |
spring.datasource.* | A HikariDataSource is auto-configured with those settings |
driver-class-name | Which JDBC driver the pool loads — not the bean type |
jpa.hibernate.ddl-auto: create-drop | Schema created at startup, dropped at shutdown |
The Jetty option is worth naming too, because it is the same shape as Q35's NGINX
option: switching containers is a dependency change, not a property. You exclude
spring-boot-starter-tomcat and add spring-boot-starter-jetty. No line of YAML can
change the embedded server.
The category test: a primitive is a kind of instrument; a metric is
something you measured with one. "Memory usage" is measured — by a Gauge named
jvm.memory.used. "CPU usage" is measured — by Gauges named system.cpu.usage and
process.cpu.usage. Neither is an instrument.
| Primitive | Answers | Typical use |
|---|---|---|
Counter | How many, monotonically increasing | Requests processed, errors raised |
Gauge | What is the value right now | Queue depth, memory used, active connections |
Timer | How many and how long | HTTP latency, method duration |
DistributionSummary | How many and how big (non-time) | Payload sizes, batch row counts |
LongTaskTimer | How long, for tasks still running | Background job duration |
Two Gauge pitfalls the exam likes: a Gauge holds a weak
reference to its source, so if the source is garbage-collected the gauge reports NaN — keep a
strong field reference. And never tag with unbounded values like user IDs; cardinality explodes.
The layer above: a MeterRegistry holds the meters, one per monitoring
system. Boot auto-configures a CompositeMeterRegistry when several
micrometer-registry-* jars are present.
Ruling the options one at a time:
| Option | Verdict | Why |
|---|---|---|
java -jar app.jar | TRUE | The fat JAR's manifest points at JarLauncher |
| WAR on Tomcat | TRUE | Servlet 3.1+ container — SpringBootServletInitializer bootstraps it |
| WAR on Jetty | TRUE | The same WAR. Jetty is equally a servlet container |
| WAR on NGINX | FALSE | NGINX is a web server / reverse proxy — it cannot execute a WAR |
Why the Jetty option is easy to leave behind: Jetty is Boot's alternative embedded server, so it reads like the "wrong" choice. But this question is about the external deployment target — and there, Tomcat and Jetty are interchangeable. If Tomcat is true, Jetty is true.
The three things a traditional WAR requires:
| Step | Why |
|---|---|
<packaging>war</packaging> | Build a WAR, not a fat JAR |
Extend SpringBootServletInitializer and override configure() | The container's ServletContainerInitializer uses it to build the context |
Embedded container scope provided | Shading Tomcat into a WAR deployed on Tomcat causes a ClassCastException |
One consequence worth holding: on an external container the WAR does not own the
port, so server.port is ignored. And a Boot 3 WAR (Servlet 6 / jakarta) cannot
deploy to Tomcat 9 (Servlet 4 / javax).
java -jar, or a WAR on any Servlet 3.1+ container — Tomcat AND Jetty. NGINX is a proxy and runs nothing. Mark the embedded server provided.web.ignoring() skips the chain — and ignoreAll() does not existantMatchers("/public/**").ignoreAll() alongside the correct
security.ignoring().antMatchers(...). The
Security clinic carries this exact drill:
"What is the difference between permitAll() and web.ignoring()?"Three of the four options were invented or misapplied, and all three are
filterable without knowing the API — ignoreAll() and filtersNone() are simply not
Spring Security methods, and anonymous() is real but does something else.
web.ignoring() | permitAll() | |
|---|---|---|
| Filter chain | Bypassed entirely | Runs in full |
| Authentication checked | No | No |
| CSRF protection | Gone | Applied |
| Security headers | Gone | Applied |
| Configured on | WebSecurity | HttpSecurity |
| Use for | Static assets only | Public endpoints |
The structural tell: the two objects are different.
web.ignoring() is configured on WebSecurity — outside the chain, which is why it
can skip it. permitAll() is configured on HttpSecurity — inside the chain, which
is why it cannot. Any option that chains a bypass method off antMatchers() alone is at the wrong
level.
public void configure(WebSecurity web) { // OUTSIDE the chain
web.ignoring().antMatchers("/public/**"); // no filters run at all
}
protected void configure(HttpSecurity http) { // INSIDE the chain
http.authorizeRequests()
.antMatchers("/public/**").permitAll(); // filters run, access granted
}
And anonymous(), the plausible one: it assigns an
AnonymousAuthenticationToken to unauthenticated users. The chain still runs from end to end —
the request just travels it as "anonymous" rather than as nobody.
web.ignoring() on WebSecurity skips the chain (losing CSRF and headers); permitAll() on HttpSecurity runs it and allows. No ignoreAll(), no filtersNone().this.inner()ClassFileTransformer. Proxies modify nothingfinal blocks CGLIB only; private and self-invocation block bothJoinPoint; @Around requires ProceedingJoinPoint, which extends itproceed() may be called 0, 1 or many times — zero for caching, many for retry. There is no @AfterFinally; @After is the finally-advice@EnableTransactionManagement + a PlatformTransactionManager bean + @Transactional. Boot gives the first twoTransactionInterceptor. There is no @Transaction@Transactional works on public only — silently. No warning, no transactiongetBean() throws, never returns null: NoSuchBeanDefinition (name) · NoUniqueBeanDefinition (type) · BeanNotOfRequiredType (class)@Autowired = Spring with required; @Inject = JSR-330 with no attributes@Value: ${} = property, #{} = SpEL. Works on fields, setters, constructor args and @Bean method args. It never validatesErrors goes immediately after the validated argument@RunWith(SpringRunner.class); JUnit 5 → nothing. Every Boot slice already carries SpringExtension@WebMvcTest loads everything that touches an HTTP request — @JsonComponent included. Never TestRestTemplate@Mock = Mockito only · @MockBean = replaces in the context · @SpyBean = wraps. Each @MockBean combination builds another context@Sql declarative · ResourceDatabasePopulator programmatic. No @SqlScript, no SqlRunnerHikariDataSource from spring.datasource.*. The driver class is a driver, not a bean typejava -jar, or as a WAR on any Servlet 3.1+ container — Tomcat and Jetty. NGINX runs nothing. Swapping the server is a dependency changeweb.ignoring() (WebSecurity) skips the chain, losing CSRF and headers; permitAll() (HttpSecurity) runs it and allowsThe section headings above tell you where to look. This set does not. Twelve questions drawn from the whole paper, in the order the exam would give them to you.
@AfterFinally ·
@SqlScript · SqlRunner · ignoreAll() · filtersNone().
Four marks on this paper, filterable without any Spring knowledge.
(5) Stop timing yourself, start pacing yourself. Two hours produced a worse score than 41 minutes. Aim for
60–75 minutes: enough to read each stem twice and rule each option, not so long that certainty decays into
doubt.
(6) Re-drill, don't re-read. Seven of these 23 are already explained on this site — a record, and it went
up in the week you wrote two new clinics. Reading builds recognition; the exam tests recall.