In this Spring Boot tutorial, we will learn to display images from different sources using the Thymeleaf view templates and to upload images to the PostgreSQL database using the Spring MVC and Thymeleaf.
1. Thymeleaf Syntax for Displaying ImagesFor quick reference, we can display an image from two sources. Either it is stored in a file in the resources directory, or stored in the backend database as a byte array.
Displaying the images from the static resources is quite simple. To display static images, the thymleaf template should refer to the file with a path relative to the ‘/src/main/resources/static‘ directory.
For displaying the small images from the database, we use the Base64.encodeBase64String(byte[] imageData) to generate the base64 encoded string from the corresponding byte array of the image. Then use ‘data:image/jpeg;base64‘ prefix to embed the image directly into HTML.
Note that the above approach is useful for small images as it increases the size of HTML. For large images, HTML size may increase substantially.
For displaying large images we can create a serve raw image bytes retrieved from the database from a Spring MVC controller that handles requests for serving images based on their IDs.
@GetMapping(value = "/images/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE)Resource downloadImage(@PathVariable Long imageId) {byte[] image = imageRepository.findById(imageId) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)) .getImageData();return new ByteArrayResource(image);}And finally updating the Thymeleaf template to use the dynamic image URLs generated by the controller.
2. Project StructureFor demo purposes, let’s create a simple team management project, in which we can add the new member’s profile into our team which will contain the basic details and the profile image of the member. For this requirement, we need three different APIs:
first API to get the form to add the profile.second API to handle the submit action of the form and insert the profile details into the database.third API to get all team member’s profiles stored in the database.Let’s walk through step by step and understand.
2.1. MavenFirst of all, we will require all the common dependencies of a simple spring web project.
spring-boot-starter-web: for Spring MVC support.spring-boot-starter-data-jpa: for storing and fetching image data from the database.spring-boot-starter-security: for securing the restricted images.org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-securityNext, we need to add support for the Thymeleaf template engine for displaying the images in UI.
org.springframework.bootspring-boot-starter-thymeleafWe will also require the dependency of the database (in our case PostgreSQL)
org.postgresqlpostgresql42.6.02.2. ModelLet’s design a POJO class named Profile.java in the model package to hold the profile details of the team member. Here we are using the ‘profileImage‘ field to store the image data for the profile image uploaded from UI.
@Entity@Data@NoArgsConstructor@AllArgsConstructor@Table(name = "profiles")public class Profile {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(nullable = false)private String fullName;@Column(nullable = false)private String designation;@Columnprivate String githubLink;@Columnprivate String twitterLink;@Columnprivate String email;@Lob@Columnprivate byte[] imageData;public String generateBase64Image() {return Base64.encodeBase64String(this.imageData);}}2.3. ServiceThe ProfileService performs business logic and inserts the profile details into the database, and gets all the team member’s profiles from the database using the repository.
@Service@Slf4jpublic class ProfileService { @Autowired private ProfileRepository profileRepository; public void save(Profile profile, MultipartFile file) {try { profile.setImageData(file.getBytes()); profileRepository.save(profile);} catch (Exception e) { log.debug("Some internal error occurred", e);} } public List getAll() {return profileRepository.findAll(); } public Optional findById(Long imageId) {return profileRepository.findById(imageId); }}The data type of the image file is MultipartFile, but we require the byte array as we are inserting the image in the byte array form into our database. Hence we have to convert the MultipartFile into the byte[] using the getBytes() method of the MultipartFile class.
2.4. RepositoryLet’s create the ProfileRepository.java interface in the repository package and use the built-in methods provided by the JPA by extending the JpaRepository interface.
public interface ProfileRepository extends JpaRepository {}3. Image Upload and Display ControllerNow, let’s design the ProfileController.java in the controller package to handle all APIs of upload and view profile details along with profile images.
@Controller@Slf4jpublic class ProfileController { @Autowired private ProfileService profileService; @GetMapping("/") public String home() {return "redirect:/profiles/view"; } @GetMapping("/profiles/view") public ModelAndView view(Model model) {return new ModelAndView("view", "profiles", profileService.getAll()); } @GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE) public Resource downloadImage(@PathVariable Long imageId) {byte[] image = profileService.findById(imageId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)).getImageData();return new ByteArrayResource(image); } @GetMapping("/profiles/add") public ModelAndView addProfile() {return new ModelAndView("addProfile", "profile", new Profile()); } @PostMapping(value = "/profiles/add", consumes = MULTIPART_FORM_DATA_VALUE) public String handleProfileAdd(Profile profile, @RequestPart("file") MultipartFile file) {log.info("handling request parts: {}", file);profileService.save(profile, file);return "redirect:/profiles/view"; }}Here we are getting all the profile details in the Profile model, but not the profile image file why?
The reason is in our model the profile image is in the form of byte[] and in the HTML form the uploaded file can’t be converted to the byte[] directly, hence we are getting this separately and converting it into the byte[] in the service layer.
4. Displaying Static Images from ‘/resources‘ DirectoryTo display some static image assets from the ‘/resources/static/images’ directory in our HTML file, we have created a header.html file inside the ‘/resources/templates/fragments’ directory. We will use in all our HTML files as a header component and display the logo and social media icons.
My Team View AddAll the highlighted images are loaded from the ‘/resources/static/images‘ directory. The icons in the profile card are also loaded from the static directory, and that code will be present in the view.html which we will see in the next section.
5. Displaying Images Fetched from DatabaseNow, let’s display the dynamic profile images of all team members that are fetched from the database. Create a view.html file inside the ‘/resources/templates/‘ directory, which will contain the HTML code to show all profile cards.
ViewAll the profile images are fetched from the database, and the rest of the images are loaded from the static directory.
6. Preventing Unauthorized Access to Sensitive ImagesIn order to prevent unauthorized access to sensitive/restricted/personal images, let’s configure the spring security. We start with creating a SecurityConfiguration.java file in the configuration package. In this configuration file, we will permit all requests for ‘static/images’ directory but to access the other directory user must be authenticated.
@Configurationpublic class SecurityConfiguration { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(request -> request.requestMatchers("/profiles/*").permitAll().requestMatchers("/").permitAll().requestMatchers("/images/*").permitAll().anyRequest().authenticated());return http.build(); }}Now, move the logo.png file which is present in the ‘/static/image’ directory to a ‘/static/private’ directory and then restart the project.
Here we can access all the images except the main logo on the top, as we’ve moved that to a protected directory containing sensitive images, and to access that user must be authenticated.
7. ConclusionThis Spring Boot tutorial taught us how to access the images from different sources into the Thymeleaf template files, how to upload a file using Spring MVC Model and Thymeleaf, and how to prevent access to sensitive images using Spring security. That’s all for displaying images with Thymeleaf and Spring Boot.
Happy Learning!
Source Code on Github