Since the launch of Stripe as a payment system, the likelihood that you have used it to make a payment is high. Have you ever wondered how you can integrate Stripe into your own application? If the answer is yes, don’t worry, we’ve got you covered. In this tutorial, you will learn how to integrate Stripe in Spring Boot using Thymeleaf.

If you have searched the internet for this topic, you might have come across an article I wrote on the same topic. If you have read the article, you already know it has some missing information. As a result, this tutorial will be an updated version that provides every step on the way.


Create a new project

To create a new project, go to Spring initializr and provide the project details. Select Maven in the Project section, Java in the Language section, and 3.1.x in the Spring Boot section.

In the Project Metadata section enter the respective sections as shown below.

  • Group – com.javawhizz
  • Artifact – stripePayment
  • Name – stripePayment, this section gets filled for you.
  • Description – Integrate Stripe in Spring Boot.
  • Package name – com.javawhizz.stripePayment, this section gets filled for you.
  • Packaging – Jar
  • Java – 17

To add the project dependencies, press the ADD DEPENDENCIES button in the Dependencies section. Add the dependencies as provided below.

  • Spring Web – Create web applications by providing core web services.
  • Lombok – Reduce boilerplate code by generating helper methods.
  • Thymeleaf – Templating engine and acts as the view for the application.
  • Validation – Add constraints to fields and validates them during form submission.

The following image shows the dependencies and the project structure of the application.

Integrate stripe in Spring Boot project structure

Click on the GENERATE button to download a zipped file of your project. After downloading the project, unzip it and import it into IntelliJ. Wait for a few seconds so that IntelliJ can download the Maven dependencies.

You also need to add the Stripe dependency to the project. Open the file named pom.xml and copy and paste the following XML into the file.


Press CTRL+SHIFT+O to load the Maven changes. This will download the Stripe API and add it to the project’s classpath.

Create application sub-packages

In IntelliJ, create the following sub-packages under src/main/java/com/javawhizz/stripePayment.

  • config
  • controller
  • model

Add public and secret keys

If you did not keep a copy of the keys, go to the Stripe dashboard to get a copy. To add the keys, open the file and them as shown below.


Load the secret key

Create a file named in the config package. Copy and paste the following code into the file.

package com.javawhizz.stripePayment.config;

import com.stripe.Stripe;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

public class Config {
    private String secretKey;

    public void  initSecretKey(){
        Stripe.apiKey = secretKey;

To begin with, add the @Configuration annotation to the class. This tells Spring that the class will contain @Bean definitions.

Although you have no @Bean definitions in the class, you can use this class to initialize the secret key. Use the @PostConstruct annotation to initialize the secret key.

Since the @PostConstruct annotation is usable in any class that supports dependency injection. This is the reason behind using it in the configuration class.

To inject the secret key from the properties file to a String variable, use the @Value annotation. Next, call Stripe.apiKey in the initSecretKey() method and assign it to the String variable.

With this in place, the secret key gets loaded only once during the lifetime of the application.

Create request DTO

Create a file named under the model package. Copy and paste the following code into the file.

package com.javawhizz.stripePayment.model;

import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class Request {
    private Long amount;

    private String email;

    @Size(min = 5, max = 200)
    private String productName;

To make a payment using Stripe, you need to have a product or service that is being charged. The Request class in this case represents our product.

The product contains the amount, email, and product name. To begin with, add the @Getter and @Setter annotations to the class to generate the helper methods.

Additionally, you also need to add the @NoArgsConstructor and @AllArgsContructor. As a result, the first annotation will help you create an empty instance of the request object out of the box. The latter will help you create an instance that expects all the fields as arguments.

Next, add annotations to the fields for validation during form submission. Add @NotNull and @Min in the amount field, @Email in the email field, and @NotBlank and @Size in the product name field.

  • @NotNull – The field must not be null.
  • @Min – The value of the field must be higher or equal to the specified value.
  • @Email – Correct format of the email entered into the field.
  • @NotBlank – The value must not be null and has no whitespace.
  • @Size – The length of the value must be between the specified numbers.

Create response DTO

Create a file named in the model package. Copy and paste the following code into the file.

package com.javawhizz.stripePayment.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class Response {
    private String intentID;
    private String clientSecret;

Once the payment is successful Stripe returns a response containing the payment details. In this case, retrieve only the payment intent ID and client secret.

The class Response will hold these properties. As you can see in the class, it only contains two fields named intent ID and client secret. The class also contains the annotations to generate helper methods and create objects.

Create payment intent controller

Create a file named in the controller package. Copy and paste the following code into the file.

package com.javawhizz.stripePayment.controller;

import com.javawhizz.stripePayment.model.Request;
import com.javawhizz.stripePayment.model.Response;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import com.stripe.param.PaymentIntentCreateParams;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

public class PaymentIntentController {
    public Response createPaymentIntent(@RequestBody Request request) 
            throws StripeException {
        PaymentIntentCreateParams params =
                        .setAmount(request.getAmount() * 100L)

        PaymentIntent intent = 

        return new Response(intent.getId(),

When you submit the products page, this controller gets called immediately by JavaScript. Don’t worry about the JavaScript code, you will write the code later. This create payment intent method gets called to create and returns a payment intent.

The payment intent helps you through the process of charging the customer. As a result, it will track the status of the payment from the payment method to a successful transaction.

Note that the method createPaymentIntent() expects the Request as the argument. The amount and product name from the request gets passed to PaymentIntentCreateParams. To set the currency, you should also pass the String USD.

Note that the email field is not part of the parameters. As a result, JavaScript loads the payment form already pre-filled with the email on the products page.

The role of the PaymentIntentCreateParams is to define the parameters for the payment. To create a payment intent, Call the static method named create() of PaymentIntent. Next, pass the reference of the params object as the argument of the method.

Since the method returns the response DTO, return an instance of the class. Finally, pass the arguments intent ID and client secret as the arguments of the method. The payment intent created by Stripe contains these values.

Create a controller for the application

Create a file named in the controller package. Copy and paste the following code into the file.

package com.javawhizz.stripePayment.controller;

import com.javawhizz.stripePayment.model.Request;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

public class AppController {
    private String publicKey;
    public String home(Model model){
        model.addAttribute("request", new Request());
        return "index";

    public String showCard(@ModelAttribute @Valid Request request,
                               BindingResult bindingResult,
                               Model model){
        if (bindingResult.hasErrors()){
            return "index";
        model.addAttribute("publicKey", publicKey);
        model.addAttribute("amount", request.getAmount());
        model.addAttribute("email", request.getEmail());
        model.addAttribute("productName", request.getProductName());
        return "checkout";

When you first access the application, it returns the product details page. The controller uses the home() method to return the products page. You will create the page later.

The hasErrors() method ensures validation of the fields before submitting the form. If there are no errors, the controller returns the checkout page. The showCard() method is in charge of this functionality. Likewise, you will create the page later.

For the payment to work, you should delegate the public key and product details to JavaScript. This is because the JavaScript code is in charge of making a request to the server to create a payment intent.

To achieve this, add the data to the page as model attributes. As a result, we will later retrieve the data from the page using Thymeleaf expressions. Once you retrieve the data, you can use it in the JavaScript code.

Create the product details page

Create a file named index.html under the package src/main/resources/templates. Copy and paste the following code into the file.

<!doctype html>
<html lang="en" xmlns:th="">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap demo</title>
    <link href=""

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <form th:action="@{/}" th:object="${request}" method="post">
                <h2>Enter product details</h2>
                <div class="mb-3">
                    <label for="email"
                           class="form-label">Email address</label>
                    <input type="email"
                           required class="form-control"
                    <div id="emailHelp"
                        We'll never share your email with anyone else.
                    <p th:if="${#fields.hasErrors('email')}"
                       style="color: red"/>
                <div class="mb-3">
                    <label for="amount"
                    <input type="number"
                           required th:field="*{amount}"
                    <p th:if="${#fields.hasErrors('amount')}"
                       style="color: red"/>

                <div class="mb-3">
                    <label for="productName"
                           class="form-label" >Product Name</label>
                    <input type="text"
                    <p th:if="${#fields.hasErrors('productName')}"
                       style="color: red"/>

                <button type="submit"
                        class="btn btn-primary">Submit</button>



<script src=""

This is the first page that gets loaded to enter the product details. The product details include the amount, email, and product name.

All the fields in the form use the hasErrors() method to show any errors when submitting the form. The data in this form get passed to the showCard() method where they are then added to the checkout page.

Create the checkout page

Create a file named checkout.html under the package src/main/resources/templates. Copy and paste the following code into the file.

<!DOCTYPE html>
<html lang="en" xmlns:th="">
    <meta charset="utf-8" />
    <title>Accept a payment</title>
    <meta name="description" content="A demo of a payment on Stripe" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="/checkout.css" />
    <script src=""></script>

    <script th:inline="javascript">


        var publicKey = /*[[${publicKey}]]*/ null;
        var amount = /*[[${amount}]]*/ null;
        var email = /*[[${email}]]*/ null;
        var productName = /*[[${productName}]]*/ null;


    <script src="/checkout.js" defer></script>
<!-- Display a payment form -->
<form id="payment-form">
    <h2>Like the content: Support JavaWhizz</h2>
    <span>You are about to make a payment of: </span>
    <span th:text="*{amount}"></span>
    <div id="link-authentication-element">
        <!--Stripe.js injects the Link Authentication Element-->
    <div id="payment-element">
        <!--Stripe.js injects the Payment Element-->
    <button id="submit">
        <div class="spinner hidden" id="spinner"></div>
        <span id="button-text">Pay now</span>
    <div id="payment-message" class="hidden"></div>

Inside the head section of the checkout page, use inline JavaScript to retrieve the data. By using Thymeleaf expressions, you can add the data to JavaScript variables. As a result, the variables are available to every JavaScript file in the application.

Note that the checkout page has a link to a JavaScript file named checkout.js. This file uses the JavaScript variables to send a request to the server to create a payment intent.

After creating the payment intent, Stripe injects the card into the checkout page. To complete the payment, you only need to enter the card details and click the button labeled Pay Now.

Implement JavaScript code for the checkout page

Create a file named checkout.js under the package src/main/resources/static. Copy and paste the following code into the file.

// This is your test publishable API key.
const stripe = Stripe(publicKey);

// The items the customer wants to buy
const request = {
    amount: amount,
    email: email,
    productName: productName

let elements;


    .addEventListener("submit", handleSubmit);

let emailAddress = '';
// Fetches a payment intent and captures the client secret

let paymentIntentID = '';
async function initialize() {
    const response = await fetch("/create-payment-intent", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),

    const { intentID, clientSecret } = await response.json();

    paymentIntentID = intentID;

    const appearance = {
        theme: 'stripe',
    elements = stripe.elements({ appearance, clientSecret });

    const linkAuthenticationElement = elements.create("linkAuthentication");

    linkAuthenticationElement.on('change', (event) => {
        emailAddress =;

    const paymentElementOptions = {
        layout: "tabs",
        defaultValues: {

    const paymentElement = elements.create("payment", paymentElementOptions);


async function handleSubmit(e) {

    const { error } = await stripe.confirmPayment({
        confirmParams: {
            // Make sure to change this to your payment completion page
            return_url: ""+paymentIntentID,
            receipt_email: emailAddress

    // This point will only be reached if there is an immediate error when
    // confirming the payment. Otherwise, your customer will be redirected to
    // your `return_url`. For some payment methods like iDEAL, your customer will
    // be redirected to an intermediate site first to authorize the payment, then
    // redirected to the `return_url`.
    if (error.type === "card_error" || error.type === "validation_error") {
    } else {
        showMessage("An unexpected error occurred.");


// Fetches the payment intent status after payment submission
async function checkStatus() {
    const clientSecret = new URLSearchParams(

    if (!clientSecret) {

    const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);

    switch (paymentIntent.status) {
        case "succeeded":
            showMessage("Payment succeeded!");
        case "processing":
            showMessage("Your payment is processing.");
        case "requires_payment_method":
            showMessage("Your payment was not successful, please try again.");
            showMessage("Something went wrong.");

// ------- UI helpers -------

function showMessage(messageText) {
    const messageContainer = document.querySelector("#payment-message");

    messageContainer.textContent = messageText;

    setTimeout(function () {
        messageContainer.textContent = "";
    }, 4000);

// Show a spinner on payment submission
function setLoading(isLoading) {
    if (isLoading) {
        // Disable the button and show a spinner
        document.querySelector("#submit").disabled = true;
    } else {
        document.querySelector("#submit").disabled = false;

Note that most of this code is available in the Stripe documentation. In spite of this, you need to make a few changes for the application to work.

For example, since we not using static data, start by creating a request object to send to the server. The request object should have the amount, email, and product name. Note that these are the JavaScript variables retrieved from the checkout page.

Since the payment form should be pre-populated with the email. Add the code for this behavior in the paymentElementOptions object. Apart from this, the code has comments that explain how the JavaScript code works.

Create a stylesheet for the checkout page

If you were keen, you might have noticed the CSS stylesheet link on the checkout page in the head section. This file styles the checkout page including the payment form.

Create a file named checkout.css under the package src/main/resources/static. Copy and paste the following code into the file.

/* Variables */
* {
    box-sizing: border-box;

body {
    font-family: -apple-system, BlinkMacSystemFont, sans-serif;
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
    display: flex;
    justify-content: center;
    align-content: center;
    height: 100vh;
    width: 100vw;

form {
    width: 30vw;
    min-width: 500px;
    align-self: center;
    box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
    0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
    border-radius: 7px;
    padding: 40px;

.hidden {
    display: none;

#payment-message {
    color: rgb(105, 115, 134);
    font-size: 16px;
    line-height: 20px;
    padding-top: 12px;
    text-align: center;

#payment-element {
    margin-bottom: 24px;

/* Buttons and links */
button {
    background: #5469d4;
    font-family: Arial, sans-serif;
    color: #ffffff;
    border-radius: 4px;
    border: 0;
    padding: 12px 16px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    display: block;
    transition: all 0.2s ease;
    box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
    width: 100%;
button:hover {
    filter: contrast(115%);
button:disabled {
    opacity: 0.5;
    cursor: default;

/* spinner/processing state, errors */
.spinner:after {
    border-radius: 50%;
.spinner {
    color: #ffffff;
    font-size: 22px;
    text-indent: -99999px;
    margin: 0px auto;
    position: relative;
    width: 20px;
    height: 20px;
    box-shadow: inset 0 0 0 2px;
    -webkit-transform: translateZ(0);
    -ms-transform: translateZ(0);
    transform: translateZ(0);
.spinner:after {
    position: absolute;
    content: "";
.spinner:before {
    width: 10.4px;
    height: 20.4px;
    background: #5469d4;
    border-radius: 20.4px 0 0 20.4px;
    top: -0.2px;
    left: -0.2px;
    -webkit-transform-origin: 10.4px 10.2px;
    transform-origin: 10.4px 10.2px;
    -webkit-animation: loading 2s infinite ease 1.5s;
    animation: loading 2s infinite ease 1.5s;
.spinner:after {
    width: 10.4px;
    height: 10.2px;
    background: #5469d4;
    border-radius: 0 10.2px 10.2px 0;
    top: -0.1px;
    left: 10.2px;
    -webkit-transform-origin: 0px 10.2px;
    transform-origin: 0px 10.2px;
    -webkit-animation: loading 2s infinite ease;
    animation: loading 2s infinite ease;

@-webkit-keyframes loading {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
@keyframes loading {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);

@media only screen and (max-width: 600px) {
    form {
        width: 80vw;
        min-width: initial;

Run and test the application

To run the application, use the command SHIFT+F10. If the application runs without any errors, you should see the following page.

integrate stripe in spring boot products page

Enter the product details and press the Submit button. Stripe creates a payment intent and injects a payment form into the checkout page. As a result, you should see the following checkout page.

integrate stripe in spring boot payment form

Since you don’t want to use your real card for the transaction. Stripe provides test cards that you can use to test the payments. Go to the Stripe documentation to get one of the test numbers to use in your application. In this example, use the number 4242 4242 4242 4242 as your card number.

In the remaining fields, you can enter random data. To complete the payment, press the Pay Now button. If the transaction is complete, you should see the following page.

integrate stripe in spring boot payment success


In this tutorial, you have learned how to integrate Stripe in Spring Boot using Thymeleaf. If you want to transition the application from test mode to live mode, update your public and secret keys.

The keys used in test mode are free for any developer to use. As a result, you need to upgrade your account to get keys that you can use in production environments.

There are other payment alternatives for Stripe including PayPal, Cash App, and Venmo. In the next tutorial, you will learn how to integrate these payment providers.

Meanwhile, try to integrate Stripe into your application using a live account. You can also check out our blog to read more articles. If you are a visual learner, this tutorial is available on YouTube. The code for the application is also available on GitHub.

Happy Hacking!


Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *