Exam Drill #14 · 9 Sep 2026 · 22 to fix · 71 drills

Fourteenth attempt — you fixed both behaviours I asked for, and both worked

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.

0
Blanks — was 2 ✓
69m
Time — in the target band ✓
17
Over-selections — was 14
+8
Questions to pass
Official result · practice exam

63% — you did not pass this time

76% required to pass · 60/60 answered · 38 correct · time used 1h 8m 59s of 2h 10m
Spring MVC 89%▲ 291 miss / 9 Spring Security 75%▲ 81 miss / 4 Spring Core 63%▼ 49 misses / 24 Data Management 63%▼ 43 misses / 8 Spring Boot 56%▼ 74 misses / 9 Testing 33%▼ 234 misses / 6

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.

Three things changed, and all three worked

Instruction after #13Attempt #13Attempt #14Result
Never leave a box blank2 blank0 blankDone — 60/60 answered
Pace at 60–75 minutes1h 59m 36s1h 8m 59sDone — 69 min, mid-band
Re-drill the decayed MVC clinicMVC 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.

Nine marks went to ticking one box too many on questions that told you the number

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.

QStem saysYou tickedThe 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"
Q4Single answer — "What is true about @AliasFor?"2"an alias for a bean"
Q20Single answer — which scope is per-ServletContext2singleton
Q29Single answer — when is BeanPostProcessor called2"Only once — before init callbacks"
Q42Single answer — the correct pointcut expression2@execution(...)
Q49Single answer — what # references in SpEL2"Properties in the application environment"
Q57Single answer — how to initialise MockMvc2standaloneSetup(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.

The failure shape, three papers running

Failure mode#12#13#14Trend
Over-selection — one wrong extra—1417Worse — now 77% of all misses
Under-selection — stopped short—75Improving
Left blank—20Fixed
Ticked only wrong options—00Still 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.

Eight repeats — and three are from the page you had me write two days ago

A new record (7 on #13, 4 on #12). The first three rows are the ones to sit with.

QTopicAlready on this siteAge
Q49#{} SpEL vs ${} placeholderReview #13 §③ — with a memory hook and 2 drills2 days
Q58Where @Value can be placedReview #13 §③ — "fields, setters, constructor params…"2 days
Q26@Mock vs @MockBean — which libraryReview #13 §⑤ — the exact three-row table2 days
Q9InitializingBean has no postConstructReview #12 Q6 — same wrong option10 days
Q50@DataJpaTest scopeTesting clinic · Review #12 closing list10 days
Q11Transaction propagationData clinic — the seven-level table4 weeks
Q40PagingAndSortingRepositoryData clinic · lesson 104 weeks
Q35 · Q54Actuator info + endpoint rosterBoot clinic — the roster table4 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.

Where the eight marks are

BucketMarksWhat it costs you
Count your ticks against the stem9Zero study. Six single-answer questions with 2 ticks, three "which N" with N+1
Spring Core — the section with no clinic924 of 60 questions live here. SpEL, lifecycle, scopes, AOP, @Value
Re-drill the eight repeats9Material you already own; three of it two days old
Testing — 4 of its 6 questions4Smallest 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.

All 22, at a glance

QTopicWhat went wrongThe answer
Q4@AliasFor+ "an alias for a bean"Aliases annotation attributes
Q9Lifecycle callback interfaces+ an InitializingBean method nameafterPropertiesSet() · destroy()
Q10JdbcTemplate return types+ JSONObjectScalars · Map · RowMapper types
Q11Defining transaction propagation+ an invented property / env var@Transactional(propagation=…) · TransactionDefinition
Q19@Transactional + @Rollback in testsUnder-selected — 4 were trueRollback is the default; @Rollback is redundant
Q20Scope tied to the ServletContext+ singletonapplication
Q22Boot + Spring MVC — "which two"Ticked 3 — + in-memory DBEmbedded container by default · replaceable with Undertow
Q23What affects component scanning+ "ComponentScanner bean" — inventedscanBasePackages · <context:component-scan>
Q26Which are Boot-specific test annotations+ @InjectMocks (Mockito)@SpringBootTest · @MockBean
Q27Environment in non-web apps+ a servlet-only sourceSystem properties · env vars · profiles
Q29When BeanPostProcessor runs+ "only once"Twice per bean, around the init callbacks
Q31RestTemplate custom headersUnder-selected — 4 were trueUse exchange(); getForEntity takes no HttpEntity
Q32@PreAuthorize expressions+ @username (bean prefix)#username · hasRole('ADMIN')
Q35What to expose via Actuator infoUnder-selected — 4 were trueVersion · git hash · description · name
Q38Advantages of DI — "which three"Ticked 4Loose coupling · centralised config · external management
Q40Spring Data repository interfacesUnder-selected — 3 were trueRepository · CrudRepository · PagingAndSortingRepository
Q42Pointcut for getters and setters+ @execution — inventedexecution(* …get*()) || execution(* …set*(*))
Q49What # references in SpEL+ "environment properties"Spring beans — #{@beanName…}
Q50@DataJpaTest — "which two"Ticked 3 — + NoSQLTestEntityManager · embedded DB if on classpath
Q54Which are Actuator endpointsUnder-selected — 3 were trueconditions · httptrace · beans
Q57Initialising MockMvc+ the .class variantstandaloneSetup(new PersonController())
Q58Where @Value can be used+ method param without @AutowiredField · method · constructor param · annotation type

Jump to a section

⓪ The counting drill — worth 9 marks ① SpEL & @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%)

⓪ The counting drill worth 9 marks on this paper

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.

The rule, in three lines

Read the number → rule each option → count before submitting

Stem saysTicks allowedWhat a wrong count costs
"Which two…" / "Which three…"Exactly that manyN+1 ticks = zero, even with all N right
"What is…" / "Which option…" / a fill-in-the-blankExactly one2 ticks = zero, even when one is correct
"Which of these are…" / "Which statements are correct"However many are true — often allStopping 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.

Memory hook: The stem's number is free information. Single answer = one tick, always. Two ticks on a one-answer question is a mark you earned and threw away.

① SpEL & @Value 2 misses · both explained here 2 days ago

Q49 · What # references in SpEL over-selected review #13 · 2 days

#{...} is the SpEL delimiter; ${...} is the property placeholder

You ticked "Spring Beans" (right) and "Properties in the application environment" (wrong). Single-answer question — the second tick threw away a mark you had.

Review #13 §③ carries this exact table. Here it is again, extended with what each prefix reaches:

SyntaxIsResolved byReaches
${...}Property placeholderThe EnvironmentApplication properties — the option you wrongly ticked
#{...}SpEL expressionThe expression parserBeans, statics, literals, arithmetic
#{@beanName.method()}Bean referenceSpEL + the contextSpring beans — the correct answer
#{systemProperties['k']}SpEL over a built-inSpELJVM system properties
#{T(Math).PI}T() type operatorSpELStatic 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 §⑨.

Memory hook: ${} = property, from the Environment. #{} = SpEL. #{@bean} = a bean. A bare # never means "a property".
Q58 · Where @Value can be used over-selected review #13 · 2 days

Five of six placements are valid — the invalid one is the method parameter without @Autowired

You ticked five true options and added the false one. You knew this material — review #13 lists the placements. One extra tick cost the mark.
PlacementWorks?Why
On a fieldYesInjected by AutowiredAnnotationBeanPostProcessor, reflection bypasses private
On a methodYesOn a setter or any @Autowired method
On a constructor parameterYesA single constructor is used implicitly — no @Autowired needed
On a method parameter, with @Autowired on the methodYesThe method is an injection point, so its parameters are processed
On an annotation typeYes@Target includes ANNOTATION_TYPE — composite annotations
On a method parameter, without @AutowiredNoSpring 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}").

Memory hook: @Value works wherever Spring already injects — field, constructor param, @Autowired method (and its params), annotation type. A bare method is not an injection point.

② Container, lifecycle & scopes 4 misses

Q9 · Lifecycle callback interfaces over-selected review #12 · same wrong option

Two interfaces, one method each — afterPropertiesSet() and destroy()

You ticked an option giving InitializingBean a method called init or postConstruct. The identical wrong option cost you Q6 on attempt #12.
RouteInitDestroyOrigin
InterfaceInitializingBean.afterPropertiesSet()DisposableBean.destroy()Spring
Annotation@PostConstruct@PreDestroyJSR-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.

Memory hook: InitializingBean.afterPropertiesSet() · DisposableBean.destroy(). @PostConstruct is a JSR-250 annotation, never an interface method.
Q29 · When BeanPostProcessor runs over-selected

Twice per bean — and the second hook is where AOP proxies are created

You ticked the correct "twice per bean" option and "only once — after instantiation but before initialization". Single answer; the second tick lost it.

Why "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
1Constructor / factory method — the instance exists
2Dependency injection (populate properties)
3Aware setters — BeanNameAware, BeanFactoryAware…
4BeanPostProcessor.postProcessBeforeInitialization
5@PostConstruct → afterPropertiesSet() → custom init-method
6BeanPostProcessor.postProcessAfterInitialization ← proxy created here
7Bean is ready for use
8On 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.

Memory hook: BPP = two hooks, wrapped around the init callbacks. postProcessAfterInitialization is where the AOP proxy is born. Destruction lives in a different interface.
Q20 · The scope tied to the ServletContext over-selected

application — not singleton, and the difference is real

You ticked singleton alongside application. Understandable — they behave identically in a simple app. The stem's words "ServletContext" and "web-aware" are what separate them.
ScopeLifecycleWeb only?Instances
singleton (default)The IoC containerNo1 per ApplicationContext
prototypeOn demandNoA new one every injection
requestOne HTTP requestYes1 per request
sessionOne HTTP sessionYes1 per user session
applicationThe ServletContextYes1 per web application
websocketOne WebSocket sessionYes1 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.

Memory hook: singleton = per container · application = per ServletContext. Three scopes are web-only: request, session, application (+ websocket).
Q27 · The Environment in non-web applications over-selected

StandardEnvironment has exactly two property sources — everything servlet is absent

You ticked a servlet-only source (JNDI, servlet context params, or servlet config). You took all three correct options; the extra was a web-only source in a non-web question.

The question is a two-class comparison, and once you see that, every option sorts itself:

AvailableClassBacked by
JVM system propertiesStandardEnvironmentSystem.getProperties()
OS environment variablesStandardEnvironmentSystem.getenv()
ProfilesThe Environment interface itselfgetActiveProfiles() / getDefaultProfiles()
JNDI propertiesStandardServletEnvironmentNeeds a container
ServletContext parametersStandardServletEnvironment<context-param>
ServletConfig parametersStandardServletEnvironmentServletConfig.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.

Memory hook: Non-web StandardEnvironment = system properties + env vars, plus profiles from the interface. Anything with "Servlet" or "JNDI" in the name needs StandardServletEnvironment.

③ Annotations & DI 2 misses

Q4 · @AliasFor over-selected

It aliases annotation attributes — nothing to do with beans

You ticked the correct answer and "it can be used to declare an alias for a bean". Single answer. Bean aliases are a real Spring feature — just not this one.

@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.

Memory hook: @AliasFor aliases attributes within an annotation. Bean names are aliased by @Bean(name={...}). Different layers.
Q38 · Advantages of DI — "which three" ticked 4

The false option contradicts itself — DI decouples behavior from construction

You ticked four options on a "which three" question, adding "DI makes code easier to trace because it couples behavior with construction". You correctly rejected the two obvious false ones.

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.

StatementVerdictWhy
Facilitates loose couplingTRUEComponents depend on abstractions, wired at runtime
Configuration externalized and centralizedTRUEJava config, XML or properties in one place
Dependencies managed external to componentsTRUEThe container owns construction and lifecycle
Easier to trace — couples behavior with constructionFALSEDI decouples them; tracing usually gets harder
Creates tight couplingFALSEThe direct opposite of DI's purpose
Reduces start-up timeFALSEWiring 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.

Memory hook: DI buys loose coupling, centralised config, external dependency management, testability. It costs traceability and a little start-up time.

④ AOP pointcuts 1 miss

Q42 · Matching getters and setters invented designator

There is no @execution — the designator is execution, unprefixed

You ticked the correct execution(...) 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.

DesignatorArgumentMatches
execution(pattern)Method signatureReturn type, class, method name, params
within(TypePattern)Type patternEvery method in matching types
bean(namePattern)Spring bean nameAll methods on that bean
@annotation(Anno)Annotation typeMethods carrying that annotation
@within(Anno)Annotation typeMethods in classes carrying it
@execution(…)—Does not exist
@bean(…)—Does not exist

Now the wildcards, which the correct answer turns on:

PatternParameters 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.

Memory hook: An @-designator always takes an annotation type. execution takes a signature and has no @. get*() = no args · set*(*) = one arg · (..) = any.

⑤ Testing 4 of its 6 questions · 33%, its worst ever

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.

Q26 · Which are Boot-specific test annotations over-selected review #13 · 2 days

Sort by library, not by whether you use it in a Spring test

You ticked @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?":

AnnotationLibraryPurpose
@SpringBootTestspring-boot-testLoads a full Boot ApplicationContext
@MockBeanspring-boot-testMock that replaces a bean in the context
@SpyBeanspring-boot-testSpy that wraps the real bean
@Mockmockito-corePlain Mockito mock — Spring never sees it
@Spymockito-corePlain Mockito spy
@InjectMocksmockito-coreInjects @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.

Memory hook: "Bean" in the name = Spring Boot (@MockBean, @SpyBean). Bare noun = Mockito (@Mock, @Spy, @InjectMocks).
Q50 · @DataJpaTest — "which two" ticked 3 Testing clinic

A slice is defined by what it excludes — and this one excludes NoSQL and JdbcTemplate

You ticked three on a "which two" question, adding "it can test both JPA and NoSQL components". Both correct options were taken.
StatementVerdictWhy
Auto-configures a TestEntityManagerTRUEWith helpers like persistFlushFind
Uses an embedded DB if one is on the classpathTRUEH2 / HSQL / Derby, replacing the real DataSource
Tests JPA and NoSQLFALSENoSQL has its own slices: @DataMongoTest, @DataRedisTest
Can be used to test JdbcTemplateFALSEThat is @JdbcTest
TestEntityManager has all EntityManager methods and moreFALSEA 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.

SliceLoadsAlso
@DataJpaTestJPA repositories, entities, TestEntityManagerTransactional, rolled back; embedded DB
@JdbcTestJdbcTemplate, DataSourceTransactional, rolled back
@DataMongoTestMongo repositories + templateEmbedded Mongo if present
@WebMvcTestControllers, advice, @JsonComponent, MockMvcNo services, no repositories
@JsonTestJackson/Gson config + testersNo web layer
Memory hook: One slice per technology. @DataJpaTest = JPA only, embedded DB, transactional and rolled back. NoSQL → @DataMongoTest · JdbcTemplate → @JdbcTest.
Q57 · Initialising MockMvc standalone over-selected

standaloneSetup takes an instance, not a Class

You ticked standaloneSetup(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).

BuilderTakesLoads a context?Use for
MockMvcBuilders.standaloneSetup(controller…)Controller instancesNoFast, isolated controller unit tests
MockMvcBuilders.webAppContextSetup(wac)A WebApplicationContextYesIntegration tests — null is an error
@AutoConfigureMockMvc + @Autowired MockMvc—YesBoot 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.

Memory hook: standaloneSetup(new Controller(deps)) — an instance, no context. webAppContextSetup(wac) — a real context. new MockMvc() never compiles.
Q19 · @Transactional + @Rollback in tests under-selected

Four of six were true — and the key fact is that rollback is already the default

Your answer: under-selected. You correctly rejected both false options.

The 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.

CombinationEffect
@Transactional aloneRolled back — the default
@Transactional + @RollbackRolled back — redundant but harmless
@Transactional + @Rollback(false)Committed — changes persist
@Transactional + @CommitCommitted — a readable alias for the above
No @TransactionalNo test-managed transaction; whatever the code does, commits

Working through the four true statements, since each is a separate idea:

StatementWhy it is true
@Rollback(false) would make testRollback failThe update would commit, so the later assertion of 0 breaks
testRollback verifies the changes did not persistThat is exactly what asserting 0 afterwards checks
@Rollback is not requiredRollback is already the default for a transactional test
The transaction rolls back, leaving inventory unchangedThe 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.

Memory hook: Transactional tests roll back by default. @Rollback = redundant · @Rollback(false) / @Commit = persist.

⑥ Spring Boot 4 misses · 56%, still the weakest after Testing

Q22 · Boot + Spring MVC — "which two" ticked 3

An in-memory database is not automatic — it needs JPA on the classpath

You ticked three on a "which two", adding "Spring MVC starts up an in-memory database by default". You correctly rejected port 8088 and "Jetty is the default".

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.

StatementVerdictThe fact
Boot starts an embedded servlet container by defaultTRUETomcat, unless you exclude it
The container can be replaced with UndertowTRUESwap the starter — Jetty and Undertow both work
Spring MVC starts an in-memory DB by defaultFALSENeeds a data starter and an embedded driver
The default port is 8088FALSE8080. 8088 is not a Spring default anywhere
Jetty is the default containerFALSETomcat 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.

Memory hook: Tomcat on 8080 by default; Jetty and Undertow are dependency swaps. No embedded DB unless a data starter AND an embedded driver are both present.
Q23 · What affects component scanning invented bean

Three real mechanisms — and you ticked a ComponentScanner bean that does not exist

You ticked "location field of ComponentScanner 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.

MechanismReal?Form
@SpringBootApplication(scanBasePackages=…)REALOverrides the default root package
@ComponentScan(basePackages=…)REALPlain Spring Java config
<context:component-scan base-package="…"/>REALXML config
@ComponentScan(basePackageClasses=…)REALType-safe — refactor-proof, worth preferring
ComponentScanner bean with a location fieldINVENTEDNo such class
spring.scan.locationINVENTEDNo 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.

Memory hook: Scanning is set by an annotation attribute or XML element — never a bean, never a property. Default = the @SpringBootApplication package and below.
Q35 · What to expose via Actuator info under-selected Boot clinic

Every non-secret is true; the only false options are the two credentials

Your answer: under-selected. You correctly rejected both the VM password and the database password — the security judgement was right.

This 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.

DatumExpose?Contributor
Application versionYesBuildInfoContributor / info.app.version
Application nameYesEnvironmentInfoContributor
Application descriptionYesEnvironmentInfoContributor
Git commit hash / branchYesGitInfoContributor ← git.properties
VM passwordNever—
Database passwordNever—

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.

Memory hook: info = public build metadata: name, description, version, git commit. Never a secret. Boot 2.6+ needs management.info.env.enabled=true.
Q54 · Which are Actuator endpoints under-selected Boot clinic roster

All three real ones are true — only the capitalised Config is invented

Your answer: under-selected. You correctly rejected Config.
EndpointReal?Shows
conditionsREALThe auto-configuration report — what applied and why
httptraceREALRecent HTTP exchanges (httpexchanges in Boot 3)
beansREALEvery bean, its type, scope and dependencies
ConfigINVENTEDThe nearest real ones are env and configprops

The roster worth holding whole, since this is now the third Actuator question in two papers:

EndpointPurpose
health · infoHealth status · build metadata — the only two exposed over HTTP by default… and only health in 2.x
beans · conditions · mappingsThe bean graph · the auto-config report · every @RequestMapping URL
env · configpropsRaw property sources · bound @ConfigurationProperties values
metrics · loggersMicrometer meters · read and write log levels at runtime
threaddump · heapdump · logfileThe last two are web-only; threaddump is JSON and works over JMX
shutdownThe 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.

Memory hook: conditions, httptrace, beans are all real. Enabled ≠ exposed: only shutdown is disabled; only health is HTTP-exposed by default.

⑦ Data Management 3 misses

Q10 · JdbcTemplate query return types over-selected

Three families of return type — and JSONObject is not one of them

You ticked JSONObject 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.

FamilyMethodReturns
ScalarsqueryForObject(sql, Integer.class)int, long, String…
Generic mapsqueryForMap(sql) · queryForList(sql)Map<String,Object> — one row, column names as keys
Domain objectsquery(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.

Memory hook: JdbcTemplate returns scalars, Maps, or your own types via RowMapper. JSON and XML are other layers. RowMapper = per row · ResultSetExtractor = per ResultSet.
Q11 · Defining transaction propagation over-selected Data clinic

Two real routes — declarative and programmatic. Neither is a property or an env var.

You ticked an invented 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.

RouteHow
Declarative@Transactional(propagation = Propagation.REQUIRES_NEW)
ProgrammaticDefaultTransactionDefinition.setPropagationBehavior(...), or on a TransactionTemplate
A propertyNo such thing
An environment variableNo such thing
A PropagationMode beanNo such type

The seven levels — from the Data clinic, and the single most reusable table in this section:

PropagationNo active transactionTransaction already active
REQUIRED (default)Start a new oneJoin it
REQUIRES_NEWStart a new oneSuspend it, start an independent one
SUPPORTSRun non-transactionallyJoin it
NOT_SUPPORTEDRun non-transactionallySuspend it, run non-transactionally
MANDATORYThrowJoin it
NEVERRun non-transactionallyThrow
NESTEDStart a new oneCreate 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.

Memory hook: Propagation is per-method: the @Transactional attribute, or a TransactionDefinition. No property, no env var. MANDATORY and NEVER are the two that throw.
Q40 · Spring Data repository interfaces under-selected Data clinic

All three real ones count — including the bare Repository marker

Your answer: under-selected. You correctly rejected the fabricated ReadOnlyRepository.

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.

InterfaceAddsUse for
Repository<T, ID>Nothing — a markerA deliberately narrow / read-only API
CrudRepository<T, ID>save, findById, findAll, delete, countBasic CRUD
PagingAndSortingRepository<T, ID>findAll(Pageable), findAll(Sort)List views
JpaRepository<T, ID>flush, saveAndFlush, getReferenceByIdJPA-specific extras
ReadOnlyRepository—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.

Memory hook: Repository (marker) → CrudRepository → PagingAndSortingRepository → JpaRepository. No ReadOnlyRepository — extend the marker instead.

⑧ Spring MVC 1 miss · 89%, recovered from 60%

MVC: 60% → 89% after one re-drill

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.

Q31 · RestTemplate with custom headers under-selected

Four of five true — the code shown is broken, and saying so is one of the true options

Your answer: under-selected. You correctly rejected the false claim that getForEntity 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.

MethodTakes an HttpEntity?Custom headers?
getForEntity(url, type, uriVars…)No — the tail is URI variablesNo
getForObject(url, type, uriVars…)NoNo
postForEntity(url, request, type)Yes — as the bodyYes
exchange(url, method, entity, type)YesYes — 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.

Memory hook: Custom headers on any method = exchange(). getForEntity's trailing arguments are URI variables, so headers silently vanish.

⑨ Spring Security 1 miss · 75%, its best since attempt #4

Q32 · @PreAuthorize expressions over-selected

# is a method parameter · @ is a bean — and the paper tested both today

You ticked @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:

PrefixIn a security expressionExample
#A method parameter, by name#username == authentication.name
@A Spring bean, then call a method on it@permissionService.canEdit(#id)
no prefixBuilt-in expressions and the root objecthasRole('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-inChecks
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 / authenticationThe 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).

Memory hook: # = method parameter · @ = bean · bare = built-in. hasRole adds ROLE_; hasAuthority does not. The expression must return a boolean.

The 22 facts, one line each

Drill these, don't read them

Mixed drill — all sections, no headings

Ten questions in exam order, with no topic label to prime you. Decide how many ticks the stem allows before you read the options.

The change to make before the next attempt (1) Keep doing the three things that worked. Zero blanks, 69 minutes, and a re-drilled clinic produced +29 points in MVC and +8 in Security. None of that is luck — repeat it exactly. (2) Count your ticks. This is the whole gap. Nine marks went to one extra tick on questions that stated or implied the answer count. You need eight. Read the stem's number first; on a single-answer question, commit to one option — a second tick can only destroy a mark, never save one. (3) Rule every option true or false in writing. 17 of 22 misses were over-selection, up from 14. It is the only failure mode that got worse, and it is the only one left that matters. (4) Invented names, still. 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.
A 30-minute routine before the next paper 5 min — the counting drill above. Six questions, all from this paper, all about how many boxes to tick. This is the nine marks. 10 min — the Testing clinic. 33% is its worst ever, and it is only 6 questions — the cheapest section on the paper to fix. 10 min — the Boot clinic. 56%, and the endpoint roster alone covers two of this paper's misses. 5 min — the mixed drill above. No headings, stems that state their own counts.
I'm your teacher — ask me anything. Say "drill the counting" for a set built purely from "which N" and single-answer stems, "drill the repeats" for the eight you've now met twice, or "quiz me on the 22" for this attempt. Spring Core is 24 of the 60 questions — 40% of the paper — and it is the one section with no clinic. It carried 9 of these 22 misses. Say "build the Core clinic" and I'll make it from every Core miss across all fourteen papers.
← Dashboard ← Review #13 Testing clinic Boot clinic Data clinic