50/60 = 83%, against a 76% pass mark. That's four marks clear, your second-best score,
and two passes in a row. Testing (40% → 100%) and Security (50% → 100%) were the two weakest sections on
#19, and neither lost a mark this time. You also finished in 39m 46s, even faster than #19.
The thing to look at is the repeats. Five of the ten misses were questions you had already got wrong
on an earlier paper, and on four of them you ticked the same wrong option again. Those five marks are the
difference between 83% and 92%.
The first fourteen papers averaged 40.3. The last six average 48.2. Only one of those six missed the pass, and #19 and #20 were both comfortably over it.
How the sizes come out. MVC at 86% only works as 6/7. Boot at 82% is 9/11 or 14/17, Data at 75% is
9/12 or 6/8, and Core at 79% is 15/19 or 19/24. Two misses could be filed under more than one section: Q7
(@EnableTransactionManagement) and Q17 (closing the context in a WAR). The most likely split is the
one shown, with Q7 under Data and Q17 under Boot. That leaves 11 questions between Testing and Security. Whatever
the exact split, Core and Data together lost seven of the ten marks.
| If you had… | Score | Result |
|---|---|---|
| Not repeated the five questions already reviewed on this site | 55 | 92% |
| …and got the five new ones | 60 | 100% |
| What actually happened | 50 | 83% — passed |
Half the misses needed no new knowledge, only remembering a review you'd already read. The five new misses share one shape too: each ticked option claims Spring can do something it can't. See Shape 2.
| Attempt #19 | Attempt #20 | Result | |
|---|---|---|---|
| Score | 49/60 (82%) | 50/60 (83%) | +1 · passed |
| Misses | 11 | 10 | −1 |
| Stopped short (no false tick) | 3 | 1 | −2 · lowest in six papers |
| Questions with a false tick | 8 | 9 | +1 |
| Left blank | 0 | 0 | Held — eighth paper running |
| Testing + Security misses | 4 | 0 | −4 |
| Core + Data + Boot misses | 6 | 9 | +3 |
| Misses already on an earlier paper | 4 of 11 | 5 of 10 | +1 |
| Time | 40m 53s | 39m 46s | −1 min |
"Take every true option" worked. #19 asked for it and the stopped-short count fell from three to one. The only one left was Q7, a question that has two true answers.
| Section | #18 | #19 | #20 | Minutes in #19's routine |
|---|---|---|---|---|
| Testing | 57% | 40% | 100% | 8 |
| Spring Security | 100% | 50% | 100% | 3 |
| Spring MVC | 80% | 80% | 86% | 3 (with Data) |
| Spring Boot | 85% | 77% | 82% | 5 |
| Spring Core | 61% | 91% | 79% | 10 — the most |
| Data Management | 89% | 92% | 75% | 3 (with MVC) |
Data had the least time and fell the most, which fits the pattern of the last four papers. Core is the exception: it had the most minutes and still fell. But look at what Core missed. Two of its four misses (Q14, Q51) are questions you have now got wrong two or three times. New drills alone don't fix those. Redoing the old drills does, which is why the repeats come first on this page.
| Q | Topic | Already on this site | Then → now |
|---|---|---|---|
| Q51 | What SpEL compiled mode can't compile | Attempt #4 (18 Aug) and #19 Q24 yesterday | Ticked "constructors" on #19 and #20. Third miss |
| Q14 | @PostConstruct / @PreDestroy order | #9 Q57, word for word. The topic was also missed on #5, #6 and #8 | Ticked "@PostConstruct runs after afterPropertiesSet()" both times |
| Q15 | show-details + a health group | #16 Q21, word for word | Ticked "details only for the group's checks" both times |
| Q45 | JdbcTemplate return types | #14 Q10, same options | Ticked JSONObject both times |
| Q7 | Enabling @Transactional | #12 Q16, word for word | Then: two invented options. Now: no false tick, but one of the two true options left out. Better. |
The practice bank repeats itself, and so do your answers. Reading a review once leaves a vague sense of "I've seen this". Under time pressure, that feeling points you back to the option you picked last time. The fix is to answer the question again, correctly, a few days later. The first drill block is those five questions as they appeared on the paper.
afterPropertiesSet()", which is the same false tick as
today. So #9's review drilled the wrong option, and the mistake that needed fixing never got a drill. That's the
review's fault, not yours. This page fixes it.
Each time you finish a drill block (every question answered), it's recorded here in this browser. Reload the page to retake a block. Questions and options reshuffle every time. Aim for a full score on every block before the next paper, and redo the repeats block on a different day from the first try.
| Drill block | Qs | Last | Best | Runs | Last done |
|---|---|---|---|---|---|
| Loading… | |||||
| Q | Topic | What went wrong | The answer |
|---|---|---|---|
| Q7 | Enabling @Transactional | Stopped short — 2 were true · #12 | @EnableTransactionManagement · nothing needed under Boot |
| Q14 | @PostConstruct / @PreDestroy | + "after afterPropertiesSet" · same as #9 | @PreDestroy before destroy() · each called once |
| Q15 | Health group + show-details | + "only the group's checks" · same as #16 | Details shown to everyone, unauthenticated too |
| Q17 | Closing the context in a WAR | + "closes automatically when the JVM exits" | ContextLoaderListener handles contextDestroyed |
| Q18 | Default HttpMessageConverters | + CsvHttpMessageConverter | MappingJackson2… · Jaxb2RootElement… |
| Q26 | @Value("${user.home}") | + "must be defined in application.properties" | The JVM system property's value |
| Q39 | JoinPoint | + "can call the method with different arguments" | Usable in @Before · gives name + args |
| Q45 | JdbcTemplate return types | + JSONObject · same as #14 | User-defined types · simple types |
| Q49 | Declaring a repository | + "implement the Repository interface" | Extend JpaRepository / Repository · @RepositoryDefinition |
| Q51 | SpEL compiled mode | + "constructors" · same as #19 | Assignment · custom resolvers · conversion service · selection/projection |
Each of these was explained on an earlier review page, and each got the same false tick again. That means the wrong option still feels right. Here's why each one is tempting, and the fact that rules it out:
| Q | The tempting option | Why it feels right | What rules it out |
|---|---|---|---|
| Q51 | SpEL can't compile constructors | new sounds like reflection, and "Constructor" starts with C like two of the real four | The two Cs are Conversion service and Custom resolvers. new Foo() names its type, so it compiles |
| Q14 | @PostConstruct after afterPropertiesSet() | "Post" sounds like "last" | @PostConstruct is run by a BeanPostProcessor's before-init hook, which runs before the init methods |
| Q15 | Details only for the group's checks | The group is the newest thing in the YAML | show-details sits on the endpoint, so it covers /actuator/health and every group |
| Q45 | JdbcTemplate returns JSONObject | REST apps return JSON all day | Spring JDBC has no dependency on any JSON library. It returns Java values and your objects |
| Q | What you ticked | What's actually true |
|---|---|---|
| Q18 | A CsvHttpMessageConverter | No such class. Converters are named after a library (Jackson, JAXB, Gson) |
| Q39 | JoinPoint can call the method with new arguments | Only ProceedingJoinPoint.proceed(args), and only in @Around |
| Q49 | Implement the Repository interface | You extend it with an interface. Spring writes the class |
| Q26 | user.home must be in application.properties | System properties are always in the Environment |
| Q17 | The context closes automatically when the JVM exits | True for a fat jar. In a WAR the server owns the JVM, and it outlives the app |
This is the "invented strictness" shape from #18 and #19, plus its mirror image, invented ability. The quickest test is to name the actual thing.
@PostConstruct / @PreDestroy ticked a false option same as #9 Q57@PostConstruct runs first, and there's a mechanical reason whyafterPropertiesSet()".
You correctly rejected "always invoked, regardless of scope".| Step | Initialisation | Who calls it |
|---|---|---|
| 1 | postProcessBeforeInitialization | Every BPP |
| 1a | @PostConstruct | CommonAnnotationBeanPostProcessor, inside step 1 |
| 2 | InitializingBean.afterPropertiesSet() | The bean factory's invokeInitMethods |
| 3 | @Bean(initMethod = "…") | Same, straight after step 2 |
| 4 | postProcessAfterInitialization | Every BPP (AOP proxies are made here) |
This ties to #19's BPP question. @PostConstruct isn't a special phase.
It's a BeanPostProcessor doing its job in the before hook. The init methods only run after
every before-hook has finished. So @PostConstruct can't come after afterPropertiesSet().
Destruction is the same order: @PreDestroy → DisposableBean.destroy() →
destroyMethod. Each callback runs once. Prototypes get @PostConstruct but
never @PreDestroy, because Spring doesn't keep track of them after handing them out.
@PostConstruct
lives inside the BPP before-hook, so it can't be last.| Can't be compiled (A-C-C-S) | Why the compiler can't | Compiles fine |
|---|---|---|
Assignment — name = 'x' | Changes state while evaluating | Constructors — new com.acme.Foo() |
| Conversion service | The target type is decided at runtime | Operators — price * qty |
| Custom resolvers / accessors | Your own code decides the lookup at runtime | Method calls, property access, literals |
Selection / projection — .?[] .![] | Builds a new collection as it goes |
The rule behind the list: the compiler turns an expression into bytecode once, so it
needs every type fixed in advance. new com.acme.Foo() spells out its type. That's as fixed as a type
gets.
For completeness: Spring 6's docs also list
overloaded operators and array construction (new int[]{1,2}) as uncompilable. Plain
operators and ordinary constructors still compile, and the exam uses the four above.
@Value("${user.home}") an invented requirement${…} searches the whole Environment, and system properties are always in ituser.home must be defined in application.properties".
You correctly rejected "uninitialised", "exception" and "null".Property source in the Environment | Plain Spring | Spring Boot |
|---|---|---|
JVM system properties (user.home, java.version, -Dx=y) | Always | Always |
| OS environment variables | Always | Always |
@PropertySource files | If declared | If declared |
application.properties | No | Yes, loaded by Boot |
application.properties is one source among several, and a low one. It isn't a
gatekeeper that every property has to pass through. Even in plain Spring with no placeholder configurer, since
Spring 4.3 the context resolves @Value placeholders against the Environment by default.
The SpEL way to write the same thing is @Value("#{systemProperties['user.home']}").
Notice the #{…} and the systemProperties map, compared with ${user.home}.
Environment from the
start. application.properties only adds more.JoinPoint an invented abilityJoinPoint can look at the call. Only a ProceedingJoinPoint can make it.@Before" and "name + arguments", and rejected "must be
last".JoinPoint | ProceedingJoinPoint | |
|---|---|---|
| Used in | @Before, @After, @AfterReturning, @AfterThrowing | @Around only |
| Read the call | getSignature(), getArgs(), getTarget(), getThis() | Same (it extends JoinPoint) |
| Run the method | No | proceed() |
| Run it with new arguments | No | proceed(Object[] args) |
| Position in the advice signature | Must be the first parameter | |
Why it has to be this way: by the time @Before advice finishes, Spring
is going to call the method anyway. @AfterReturning runs after it's already been called. Only
@Around sits in place of the call, so only it gets the ability to make the call.
(@Before can stop the call by throwing an exception, but it can't change the arguments.)
@Around →
ProceedingJoinPoint → proceed(newArgs).@Transactional stopped short #12 Q16No false tick, so you took one of the two true options and stopped. On #12 you ticked the two invented options, so this is real progress.
| Setup | What enables @Transactional |
|---|---|
| Plain Spring | @EnableTransactionManagement on a @Configuration class, plus a PlatformTransactionManager bean |
| Spring Boot | Nothing. TransactionAutoConfiguration applies @EnableTransactionManagement for you once a transaction manager exists (e.g. from spring-boot-starter-data-jpa or -jdbc) |
@Configuration(enableTransactions=true) · @EnableDatabaseTransactions | Neither exists |
JdbcTemplate return types ticked a false option same as #14 Q10JSONObject, the same as on #14.
You correctly rejected Properties and XMLObject.| You get back | Method |
|---|---|
A simple type: Integer, String, LocalDate… | queryForObject(sql, Integer.class) |
| A list of simple types | queryForList(sql, String.class) |
One row as Map<String, Object> | queryForMap(sql) |
Many rows as List<Map<…>> | queryForList(sql) |
| Your own types | query(sql, RowMapper<T>) · ResultSetExtractor<T> |
JSONObject · XMLObject · Properties | None. Spring JDBC doesn't depend on those libraries |
If you really want JSON, map each row into your own type with a RowMapper, then let
Jackson serialise that type in the web layer. Each layer does one job.
JdbcTemplate returns scalars, maps, and your own objects. JSON is
the web layer's job.| Approach | Valid? | Methods you get for free |
|---|---|---|
interface UserRepo extends JpaRepository<User, Long> | Yes | CRUD + paging + sorting + JPA extras (flush, batch delete…) |
interface UserRepo extends Repository<User, Long> | Yes | None. Only the query methods you declare |
@RepositoryDefinition(domainClass = User.class, idClass = Long.class) on a plain interface | Yes | None. The annotation replaces the generic types |
class UserRepo implements Repository<User, Long> | No | You'd be writing the class yourself, and that's the whole thing Spring Data does for you |
Hand-written code is still possible, through a fragment: declare
UserRepoCustom, write UserRepoCustomImpl, and have UserRepo extend both
JpaRepository and UserRepoCustom. The repository itself stays an interface.
show-details ticked a false option same as #16 Q21management:
endpoint:
health:
show-details: always # on the endpoint → /actuator/health AND every group
group:
custom: # any name you like
include: diskSpace, ping # → /actuator/health/custom
| URL | Runs | Details? |
|---|---|---|
/actuator/health | Every indicator: db, diskSpace, ping… | Yes, for everyone |
/actuator/health/custom | diskSpace + ping only | Yes, inherited from the endpoint |
A group can override with its own show-details, but this YAML doesn't. Nothing is hidden
or overridden, and diskSpace and ping are built in, so nothing fails.
Two corrections to the exam's explanation. (1) It says when-authorized
means "users with the ACTUATOR role". That was Boot 1.x. In Boot 2+ it means any authenticated user, or, if you set
management.endpoint.health.roles, a user with one of those roles. (2) The probes property is
management.endpoint.health.probes.enabled. management.health.probes.enabled is the old 2.3
name.
show-details on the endpoint covers the endpoint and all its groups.
A group is a named subset with its own URL, nothing more, unless it sets its own options.ContextLoaderListener.| Deployment | Who owns the JVM | How the context closes |
|---|---|---|
| Fat jar, embedded server | Your app | JVM shutdown hook, registered by SpringApplication |
| WAR on Tomcat / WildFly / etc. | The application server | ContextLoaderListener.contextDestroyed(), when the server stops the web app |
| Tests, CLI code | Your code | context.close() or try-with-resources |
Why "when the JVM exits" fails for a WAR: one server JVM runs many apps, and you can
undeploy or redeploy a single app while the JVM keeps running for weeks. If the app waited for the JVM to exit, its
connection pools and threads would leak on every redeploy. So SpringBootServletInitializer registers a
ContextLoaderListener (a ServletContextListener) and closes the context in
contextDestroyed. Boot also turns its own JVM shutdown hook off for WAR deployments.
contextDestroyed.HttpMessageConverters an invented classCsvHttpMessageConverter.
You correctly took both true options and rejected XlsHttpMessageConverter.| Registered | Converter | Handles |
|---|---|---|
| Always | ByteArrayHttpMessageConverter · StringHttpMessageConverter · ResourceHttpMessageConverter · AllEncompassingFormHttpMessageConverter | bytes · text · files · forms |
| If Jackson is present | MappingJackson2HttpMessageConverter | JSON |
| If Jackson XML is present | MappingJackson2XmlHttpMessageConverter | XML |
| If JAXB is present | Jaxb2RootElementHttpMessageConverter | XML |
| If Gson / JSON-B is present | GsonHttpMessageConverter · JsonbHttpMessageConverter | JSON |
| Never | CsvHttpMessageConverter · XlsHttpMessageConverter | These don't exist in Spring |
If you need CSV, you write your own converter (extend AbstractHttpMessageConverter) and
register it with WebMvcConfigurer.extendMessageConverters, or declare it as a bean in Boot.
@PostConstruct runs inside the BPP before-hook, so it's first. Prototypes never get @PreDestroyshow-details on the endpoint covers every group. A group is a named subset with its own URLJdbcTemplate returns scalars, maps and your own objects. Never JSONObject@EnableTransactionManagement in plain Spring; nothing in Boot. Both are true answers@RepositoryDefinition). Never "implement"${…} searches the whole Environment. System properties and env vars are always in it@Around can make the call: ProceedingJoinPoint.proceed(args). JoinPoint only readsContextLoaderListener.contextDestroyed. The server owns the JVMTen questions with no topic label to prime you. For each option, name the class or method that would do it. If you can't, it's false.