Define and Configure Log Groups
This allows to configure a group of loggers at the same time |
-
Define a log group named
myaspect
with two packagesapplication.propertieslogging.group.myaspect=com.hascode.package1,com.hascode.package2
-
Configure the log group and set all loggers to level
TRACE
application.propertieslogging.level.myaspect=TRACE
-
This is also possible as parameter on startup
java -Dlogging.level.myaspect=TRACE myapp.jar
Use JUnit 5 with Spring Boot
Use newer versions of Surefire and Failsafe plugins:
<properties>
[..]
<maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
<maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version>
</properties>
Remove JUnit from the test-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- Exclude JUnit 4 from starter-test (and all other related test-starter, i.e
those for security and project reactor -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
Spring Boot JPA / Hibernate Debugging, Profiling and extended Logs
Add the following lines to your application.properties
or better to a separate property file for
activation with a dedicated Spring Boot profile:
# Full generated queries
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
# Aggregate Query Statistics (needs logger org.hibernate.stat set to at least DEBUG)
spring.jpa.properties.hibernate.generate_statistics=true
# Increase Log Level for Hibernate
logging.level.org.hibernate.SQL=TRACE
logging.level.org.hibernate.stat=TRACE
Add settings to the EntityManagerFactoryBean
generator method in your configuration class (inject org.springframework.core.env.Environment
in the target class!):
properties.put("hibernate.generate_statistics",
environment.getRequiredProperty("spring.jpa.properties.hibernate.generate_statistics")
Spring Boot Quick Security Config
Maven Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Configuration class
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/info")
.csrf().disable()
.authorizeRequests()
.anyRequest().hasRole("API_USER")
.and()
.httpBasic();
}
@Autowired
public void configureUsers(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("secret").roles("API_USER");
}
}
Configure SSL/TLS for Webservices WebserviceTemplate
Using Apache httpcomponents as HTTP client
We just need to set the following properties:
-
server.ssl.key-store
: the path to the keystore used .. e.g.classpath:keystore/keystore.jsk
-
server.ssl.key-store-password
: the keystore password .. e.g.changeit
package com.hascode.sample.config;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.http.ClientHttpRequestMessageSender;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@Configuration
public class TlsConfigModule {
private final Resource keyStore;
private final String keyStorePassword;
public TlsConfigModule(@Value("${server.ssl.key-store}") Resource keyStore, @Value("${server.ssl.key-store-password}") String keyStorePassword) {
this.keyStore = keyStore;
this.keyStorePassword = keyStorePassword;
}
@Bean
public WebServiceTemplate webServiceTemplate()
throws KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, CertificateException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(keyStore.getInputStream(), keyStorePassword.toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(ks, keyStorePassword.toCharArray()).build());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
ClientHttpRequestMessageSender clientHttpRequestMessageSender = new ClientHttpRequestMessageSender(requestFactory);
return new WebServiceTemplateBuilder().messageSenders(clientHttpRequestMessageSender).build();
}
}
Bypass / Disable SSL Check/Validation for Webservices HTTP Client
Never enable for production! |
The following sample allows to switch on/off the validation of SSL certificates in the HTTP client.
This fixes errors like
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This is the property used to switch off the validation if set to off
:
ssl.validation=off
And this is our configuration switching it off:
package com.hascode.example;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
@Configuration
@ConditionalOnProperty(name = "ssl.validation", havingValue = "off")
public class SSLValidationBypass {
@PostConstruct
private void ignoreCertificates() {
System.err.println("""
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!! SSL CERTIFICATE VALIDATION DISABLED !!! NEVER USE IN PRODUCTION !!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
""");
var = new TrustManager[] {new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
try {
var sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {}
}
}
Solve Spring Fox Exception
The following exception might be raised without further explanation when using Spring Fox in a Spring Boot app.
Consider moving to SpringDoc OpenAPI |
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.16.jar:5.3.16] at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.16.jar:5.3.16] at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.16.jar:5.3.16] at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.16.jar:5.3.16] at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.16.jar:5.3.16] at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.16.jar:5.3.16] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.16.jar:5.3.16] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.4.jar:2.6.4] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740) ~[spring-boot-2.6.4.jar:2.6.4] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415) ~[spring-boot-2.6.4.jar:2.6.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) ~[spring-boot-2.6.4.jar:2.6.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) ~[spring-boot-2.6.4.jar:2.6.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.4.jar:2.6.4] at com.hascode.sample.SampleApp.main(CrdReplicationApp.java:17) ~[classes/:na] Caused by: java.lang.NullPointerException: null at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0] at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113) ~[springfox-core-3.0.0.jar:3.0.0] at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89) ~[springfox-spi-3.0.0.jar:3.0.0] at java.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469) ~[na:na] at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:na] at java.base/java.util.TimSort.sort(TimSort.java:220) ~[na:na] at java.base/java.util.Arrays.sort(Arrays.java:1515) ~[na:na] at java.base/java.util.ArrayList.sort(ArrayList.java:1749) ~[na:na] at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:392) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:81) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0] at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.withDefaults(AbstractDocumentationPluginsBootstrapper.java:107) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.buildContext(AbstractDocumentationPluginsBootstrapper.java:91) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.bootstrapDocumentationPlugins(AbstractDocumentationPluginsBootstrapper.java:82) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100) ~[springfox-spring-web-3.0.0.jar:3.0.0] at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.16.jar:5.3.16] … 14 common frames omitted
Add the following property to your application.properties
:
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
Spring SOAP Client/Server Webservices Debug Logging
Add the following loggers to the application.properties
:
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.ws.client.MessageTracing.sent=DEBUG
logging.level.org.springframework.ws.server.MessageTracing.sent=DEBUG
logging.level.org.springframework.ws.client.MessageTracing.received=TRACE
logging.level.org.springframework.ws.server.MessageTracing.received=TRACE
Debug Mock MVC Http Response
resultActions.andDo(MockMvcResultHandlers.print()); // to print it
// or
MvcResult result = springMvc.perform(MockMvcRequestBuilders
.get("/teh-url").accept(MediaType.APPLICATION_JSON)).andReturn();
String content = result.getResponse().getContentAsString();
// or
.alwaysDo(MockMvcResultHandlers.print())
Solving Keycloak Circular Dependency
Using the spring-boot-starter-keycloak
when migratin to Spring Boot >=2.6 there may be raised the following exception:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌──→──┐ | keycloakSecurityConfig (field private org.keycloak.adapters.KeycloakConfigResolver org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter.keycloakConfigResolver)
What helps is to add a custom configuration class which exposes the KeycloakSpringBootConfigResolver
:
package com.hascode.config;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class KeycloakFix {
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Dump Spring Boot Properties to the Log
Sometimes useful for debugging …
DebuggingPropertyDumper.java
package com.hascode.example.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.stream.StreamSupport;
@Component
public class DebuggingPropertyDumper {
private static final Logger log = LoggerFactory.getLogger(DebuggingPropertyDumper.class);
private static final List<String> propNameFilter = List.of("secret", "password", "credential", "cred");
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
final Environment environment = event.getApplicationContext().getEnvironment();
log.info("====== Dump Environment and Configuration ======");
log.info("== Active profiles: {}", Arrays.toString(environment.getActiveProfiles()));
final MutablePropertySources sources = ((AbstractEnvironment) environment).getPropertySources();
StreamSupport.stream(sources.spliterator(), false)
.filter(ps -> ps instanceof EnumerablePropertySource)
.map(ps -> ((EnumerablePropertySource<?>) ps).getPropertyNames())
.flatMap(Arrays::stream)
.distinct()
.sorted()
.filter(propName -> !propNameFilter.contains(propName))
.forEach(prop -> log.info("Property '{}': '{}'", prop, environment.getProperty(prop)));
log.info("===========================================");
}
}
OpenAPI Code Generation for Spring 3 with Maven
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<!-- our OpenAPI source spec -->
<inputSpec>${project.basedir}/src/main/resources/openapi.yaml</inputSpec>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<!-- use Java 8 time API -->
<typeMappings>
<typeMapping>DateTime=ZonedDateTime</typeMapping>
<typeMapping>duration=Duration</typeMapping>
</typeMappings>
<importMappings>
<importMapping>ZonedDateTime=java.time.ZonedDateTime</importMapping>
<importMapping>Duration=java.time.Duration</importMapping>
</importMappings>
<!-- define packages for API and Model -->
<apiPackage>io.hascode.myapp.api</apiPackage>
<modelPackage>io.hascode.myapp.model</modelPackage>
<generatorName>spring</generatorName>
<!-- spring boot 3 delegates -->
<configOptions>
<useSpringBoot3>true</useSpringBoot3>
<delegatePattern>true</delegatePattern>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>