Some tips to help you debug Spring controller not working. There can be many reasons why this can happen. Here is a checklist:
- Have you put appropriate annotations on your class(es)? Putting the
SpringApplicationannotation will enable Spring Boot autowiring on the package and its sub-packages. - If you are building an uber jar, unzip the jar and verify it meets the Spring Boot executable jar format.
- If all else fails, there is no option but to step through the code to see why its not behaving as expected. Run the Spring application in debug mode like this:
LOGGING_LEVEL_ROOT=DEBUG \
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=\*:5005 \
-Dlogging.level.root=debug ...
In separate terminal window type:
jdb -attach 5005
Now you want to set a breakpoint at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan:
stop at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan
Then run the application by typing run:
run
This will start running the application and break when it hits doScan which scans class(es) for bean definitions. A sample call stack looks like this for reference:
[1] org.springframework.boot.loader.net.protocol.jar.JarUrlClassLoader.findResources (JarUrlClassLoader.java:79)
[2] java.lang.ClassLoader.getResources (ClassLoader.java:1,483)
[3] org.springframework.core.io.support.PathMatchingResourcePatternResolver.doFindAllClassPathResources (PathMatchingResourcePatternResolver.java:382)
[4] org.springframework.core.io.support.PathMatchingResourcePatternResolver.findAllClassPathResources (PathMatchingResourcePatternResolver.java:365)
[5] org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources (PathMatchingResourcePatternResolver.java:334)
[6] org.springframework.core.io.support.PathMatchingResourcePatternResolver.findPathMatchingResources (PathMatchingResourcePatternResolver.java:558) -->
[7] org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources (PathMatchingResourcePatternResolver.java:330)
[8] org.springframework.context.support.AbstractApplicationContext.getResources (AbstractApplicationContext.java:1,442)
[9] org.springframework.context.support.GenericApplicationContext.getResources (GenericApplicationContext.java:262)
[10] org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents (ClassPathScanningCandidateComponentProvider.java:422)
[11] org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents (ClassPathScanningCandidateComponentProvider.java:317)
[12] org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan (ClassPathBeanDefinitionScanner.java:276)
[13] org.springframework.context.annotation.ComponentScanAnnotationParser.parse (ComponentScanAnnotationParser.java:128)
[14] org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass (ConfigurationClassParser.java:289)
[15] org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass (ConfigurationClassParser.java:243)
[16] org.springframework.context.annotation.ConfigurationClassParser.parse (ConfigurationClassParser.java:196)
[17] org.springframework.context.annotation.ConfigurationClassParser.parse (ConfigurationClassParser.java:164)
[18] org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions (ConfigurationClassPostProcessor.java:415)
[19] org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry (ConfigurationClassPostProcessor.java:287)
[20] org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors (PostProcessorRegistrationDelegate.java:344)
[21] org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors (PostProcessorRegistrationDelegate.java:115)
[22] org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors (AbstractApplicationContext.java:779)
[23] org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:597)
[24] org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh (ServletWebServerApplicationContext.java:146)
[25] org.springframework.boot.SpringApplication.refresh (SpringApplication.java:738)
[26] org.springframework.boot.SpringApplication.refreshContext (SpringApplication.java:440)
[27] org.springframework.boot.SpringApplication.run (SpringApplication.java:316)
[28] org.springframework.boot.SpringApplication.run (SpringApplication.java:1,306)
[29] org.springframework.boot.SpringApplication.run (SpringApplication.java:1,295)
The best resource I found to learn Spring internals is this talk.
Other Useful Information
- Maven uses ‘start-class’ when the parent POM is being used and ignores ‘spring-boot.run.main-class’
- Not able to override the mainClass defined in pom.xml with command line flag when running an application via the spring boot maven plugin
- setting spring-boot.run.main-class does not make it accessible through System.getProperty
Running Spring boot application without Maven
The challenge is how to get the classpath? Today I learned this command from ChatGPT:
tr '\0' ' ' < /proc/<PID>/cmdline
Its an incredibly useful command as it shows the command that launched a Linux process. E.g., when you run a spring boot application using mvn spring-boot:run, above command can reveal the command that Maven runs behind-the-scenes that actually spins up the java process. It looks like this e.g.:
java -classpath /home/ubuntu/apache-maven-3.9.9/boot/plexus-classworlds-2.8.0.jar -Dclassworlds.conf=/home/ubuntu/apache-maven-3.9.9/bin/m2.conf -Dmaven.home=/home/ubuntu/apache-maven-3.9.9 -Dlibrary.jansi.path=/home/ubuntu/apache-maven-3.9.9/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/ubuntu/project-dir org.codehaus.plexus.classworlds.launcher.Launcher spring-boot:run -Dspring-boot.run.jvmArguments=-Djava.library.path=lib -DJWT_SECRET_KEY= -Dspring-boot.run.main-class=my.web.WebApplication -Dspring-boot.run.arguments=--server.port=8080
It looks like there are 2 java processes that are spawned. One is above and there is yet another java process that is like below:
java -XX:TieredStopAtLevel=1 -Djava.library.path=lib -DJWT_SECRET_KEY= -cp <full_class_path> my.web.WebApplication --server.port=8080
This is incredibly useful as it gives you the full classpath you need if you later want to launch without Maven. Many times I have to use Maven just to launch an application as constructing the humungous classpath is impossible manually.
Pro Tip: Btw you do not want -XX:TieredStopAtLevel=1. In pom.xml always set optimizedLaunch=false (yes intuitively it feels wrong but is the right thing to do) under Spring-Boot-Maven-plugin:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<optimizedLaunch>false</optimizedLaunch>
</configuration>
</plugin>
It will get rid of -XX:TieredStopAtLevel=1 when the spring boot maven plugin launches your application.