Build your own Spring Boot Starter

Ariel Segura
5 min readNov 5, 2018

--

Brief introduction to Spring Boot Starters and a guide to build your own starter.

If you ever had the chance to built a Spring Boot Application, you probably had to add some dependencies and create a couple of beans.

Some beans

The beans we’re talking about are not eatable though. They’re reusable objects that live in the spring’s context. Spring Beans have a thousand more properties than reusability, but that definition is enough for us to move forward.

For instance, one may want to connect an application to AWS S3, to upload a file. Let’s say you create an S3 Client for that:

AmazonS3 s3 = AmazonS3ClientBuilder.defaultClient();

Now you can use that client on your application to upload files, like:

s3.putObject(bucket_name, key_name, new File(file_path));

So far so good.

The problem is that you may not want to create an S3 Client on every transaction you have, you want to reuse that client. That’s when Spring Beans become useful. A bean can be created like this:

@Bean
public AmazonS3 amazonS3Client() {
return AmazonS3ClientBuilder.defaultClient();
}

And you can inject it in any component may need it:

@Component
public class MyComponent {
... @Autowired
private AmazonS3 amazonS3Client;
...

All good! But, it turns out that you’re a lazy programmer and you’d rather spending time in things that matter and not in adding dependencies or creating beans. Well, luckily, Spring came up with the idea of Spring Boot Starters.

What the hell are Spring Boot Starters?

Spring Boot Starters are just dependencies that are autoconfigurables. This means that all you have to do is:

  1. Add the dependency into your dependencies section
  2. Enable autoconfigurations through Spring’s annotation @EnableAutoConfiguration in your Spring Application:
@SpringBootApplication
@EnableAutoConfiguration
public class Application {
...

Under the hood, the starter is going to tell Spring what is its auto configuration class, and Spring is going to invoke it when loading the context. This configuration class will create all the beans you need by default.

Creating your Spring Boot Starter

Ok, it’s time to code. Let’s say you have two Spring Boot Applications and you’d like to add a feature to both of them, by reusing code.

Forget about AWS S3 and let’s add a simple feature: every time we receive a new request in our microservice, we print “Hello World!” in console.

In Spring, that can be implemented as a filter:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAwesomeFilter extends OncePerRequestFilter {

private Logger logger = LoggerFactory.getLogger(MyAwesomeFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
logger.info("Hello World!");
}
}

Now let’s create our autoconfiguration class, so that Spring creates our filter automatically:

@Configuration
@ConditionalOnProperty(name = "my.filter.enabled", havingValue = "true", matchIfMissing = true)
public class StarterExampleAutoConfiguration {

@Bean
public FilterRegistrationBean mySuperFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MyAwesomeFilter());
return registration;
}

}

This configuration will create a new Bean FilterRegistrationBean that contains the filter we created.

Our last step is to tell Spring what’s our autoconfiguration class. For this, we’ll have to create a new file called spring.factories. This file must be located in the META-INF folder -> src/main/resources/META-INF/spring.factories:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.github.arielsegura.starterexample.StarterExampleAutoConfiguration

Note: Consider renaming your autoconfiguration class if needed.

Testing your starter

Now it’s time to test your starter. For that, we need to create a Spring Boot Application:

@SpringBootApplication
@EnableAutoConfiguration
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

Before moving forward, let’s add a test to ensure our app can load:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class SimpleTest {
@Test
public void contextLoads() throws Exception {
// application test well configured
}
}

Now let’s add a test case that uses our filter:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyAwesomeFilterIntegrationTest {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

@Test
public void simpleRequestShouldUseMyAwesomeFilter() throws Exception {
restTemplate.getForObject("http://localhost:" + port + "/",
String.class);
}
}

This test will simply hit our service. After running this test, we’ll see the following output in the logs:

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.17.RELEASE)
2018-11-04 23:17:43.105 INFO 49164 --- [ main] i.g.a.s.MyAwesomeFilterIntegrationTest : Starting MyAwesomeFilterIntegrationTest on localhost with PID 49164 (started by arielsegura in *****)
2018-11-04 23:17:43.106 INFO 49164 --- [ main] i.g.a.s.MyAwesomeFilterIntegrationTest : No active profile set, falling back to default profiles: default
2018-11-04 23:17:43.135 INFO 49164 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@c730b35: startup date [Sun Nov 04 23:17:43 ART 2018]; root of context hierarchy
2018-11-04 23:17:45.135 INFO 49164 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 0 (http)
2018-11-04 23:17:45.183 INFO 49164 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-11-04 23:17:45.184 INFO 49164 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.34
2018-11-04 23:17:45.303 INFO 49164 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-11-04 23:17:45.303 INFO 49164 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2170 ms
2018-11-04 23:17:45.356 INFO 49164 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-11-04 23:17:45.356 INFO 49164 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'myAwesomeFilter' to: [/*]
2018-11-04 23:17:45.585 INFO 49164 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 61800 (http)
2018-11-04 23:17:45.592 INFO 49164 --- [ main] i.g.a.s.MyAwesomeFilterIntegrationTest : Started MyAwesomeFilterIntegrationTest in 3.761 seconds (JVM running for 4.475)
2018-11-04 23:17:45.719 INFO 49164 --- [o-auto-1-exec-1] i.g.a.starterexample.MyAwesomeFilter : Hello World!
2018-11-04 23:17:45.734 INFO 49164 --- [ Thread-3] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@c730b35: startup date [Sun Nov 04 23:17:43 ART 2018]; root of context hierarchy

If you take a look at the logs, you’ll see that our filter is actually logging Hello World! when the request arrives:

2018-11-04 23:17:45.719  INFO 49164 --- [o-auto-1-exec-1] i.g.a.starterexample.MyAwesomeFilter     : Hello World!

Conclusions

In this blog we mentioned why starters are important and we showed how easy implementing your own starter could be. Overall, starters are super useful to reuse code and components. They’re autoconfigurables and they normally can be configurable by properties as well.

In Github you can see the full example.

If you liked this post, please share it. Don’t forget to leave feedback on the comments :)

--

--

Ariel Segura

Experienced Software Engineer and aspiring entrepreneur. I write to share knowledge and learnings. Passionate about games, music and technology.