Define and Configure Log Groups

This allows to configure a group of loggers at the same time
  1. Define a log group named myaspect with two packages

    application.properties
    logging.group.myaspect=com.hascode.package1,com.hascode.package2
  2. Configure the log group and set all loggers to level TRACE

    application.properties
    logging.level.myaspect=TRACE
  3. 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:

application.properties
# 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:

  1. server.ssl.key-store: the path to the keystore used .. e.g. classpath:keystore/keystore.jsk

  2. server.ssl.key-store-password: the keystore password .. e.g. changeit

TlsConfigModule.java
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:

application.properties
ssl.validation=off

And this is our configuration switching it off:

SSLValidationBypass
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
pom.xml
<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:

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:

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:

APPLICATION FAILED TO START

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:

KeycloakFix.java
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

pom.xml
<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>