SEO: Dynamic title and meta description with Tiles and Spring MVC

Every webmaster should know by now, that the <title> of a webpage, is one of the most important factors for ranking in the search results. Not only it is the title of the tab or browser windows, but it’s also the first line people see in the search results, followed by the URL and the snippet(this is usually the content of the <meta name="description"/> combined maybe with a date):

Search results - print screen snippet

Search results – print screen snippet

Octocat Source code for this post is available on Github - is an open source project.

You’ve seen in a previous post – SEO: Friendly URL construction with Spring MVC – how to build (search engine) friendly URLs (or permalinks). Well, in this post I will present how to generate dynamic titles and meta descriptions with Tiles on top of a Spring MVC application, which currently powers

Note: A prerequisite to understanding this is to have some knowledge of integrating Tiles and Spring. You can see my posts Spring MVC and Apache Tiles integration example, Spring 3 and Tiles 2 integration and Upgrade from Tiles 2 to Tiles 3 on Spring MVC 3 application for that.

Of course your titles should be descriptive, concise, unique and accurate. Of course you your the meta description tag should have reasonable and relevant data for the page, so that it can be used by search engines to show in the snippet when it contains the keyword the searcher was looking for. There are plenty of resources on the web concerning that, some of which I listed in the resources section, but my focus in this post is on technicalities and how to populate these tags dynamically. So here we go.

After learning some of the Tiles concepts, let’s have a look at the Tiles template:

<%@ include file="/WEB-INF/template/includes.jsp" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>

		<meta name="google-site-verification" content="ZkFgaVcUEQ5HhjAA8-LOBUfcOY8Fh2PqiBqvM2xcFk0" />
		<title><tiles:insertAttribute name="title" ignore="true"/></title>
		<meta name="description" content="<tiles:insertAttribute name="page_description" ignore="true"/>">
		<link href="<c:url value="/static/css/style.min.m.css?v=1.4"/>" rel="stylesheet" type="text/css"/>
		<link rel="icon" href="<c:url value="/static/images/favicon.ico"/>" type="image/x-icon" />
		<link rel="shortcut icon" href="<c:url value="/static/images/favicon.ico"/>" type="image/x-icon" />
		<meta charset="utf-8">
		<meta property="og:image" content="<tiles:insertAttribute name="og_image" ignore="true"/>" />
		<meta property="og:title" content="<tiles:insertAttribute name="og_title" ignore="true"/>" />
		<meta property="og:description" content="<tiles:insertAttribute name="og_desc" ignore="true"/>"/>
		<link rel="stylesheet" href="<tiles:insertAttribute name="jquery_ui_css" ignore="true"/>" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
    	<div id="banner">
			<tiles:insertAttribute name="header" />
		<div class="clear"></div>
		<tiles:insertAttribute name="navigation_bar" />
		<div class="clear"></div>
		<div id="page">
			<tiles:insertAttribute name="content" />
		<div class="clear"></div>
		<div id="footer_wrapper">
			<tiles:insertAttribute name="footer" />

This represents the layout of the page and the <title> (line 10) and <meta name="description"> (line 11) tags are filled with the help of Tiles attributes.

To recap, an attribute is a gap in a template that needs to be filled in your application. An attribute can be of three types:

  • string: it is a string to be directly rendered as it is.
  • template: it is a template, with or without attributes. If it has attributes, you have to fill them too to render a page.
  • definition: it is a reusable composed page, with all (or some) attributes filled
  • Let’s have look now at the Tiles definition (podcastDetails), which renders the podcast details web page:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "">
        <definition name="podcastDetails" extends="defaultTemplate">
        	<put-attribute name="title" expression="${podcast.title}"/>
     	    <put-attribute name="page_description" expression="${podcast.description}"/>
        	<put-attribute name="navigation_bar" value="/WEB-INF/jsp/navigation_bar/podcast_details_navigation_bar.jsp" />
        	<put-attribute name="content" value="/WEB-INF/jsp/podcastDetails.jsp"/>
        	<put-attribute name="og_title" expression="${podcast.title}"/>
     	    <put-attribute name="og_desc" expression="${podcast.description}"/>
     	    <put-attribute name="og_image" expression="${podcast.urlOfImageToDisplay}"/>

    Notice here the line which fills the title of the web page : <put-attribute name="title" expression="${podcast.title}"/>. There is the name="title", which is the same name as the attribute defined in the Template (<title><tiles:insertAttribute name="title" ignore="true"/></title>). The value of the attribute is an attribute expression – expression="${podcast.title}" - which is interpreted in Unified EL. The Expression Language (EL) is supported since Tiles 2.1. The podcast.title is a property of a Podcast bean supplied by the Spring controller:

    @RequestMapping(value="{podcastId}/*", method=RequestMethod.GET)
    public String getPodcastDetails(@PathVariable("podcastId") int podcastId,
    							 ModelMap model,
    							 HttpServletRequest httpRequest) throws BusinessException{
    	LOG.debug("------ getPodcastDetails : Received request to show details for podcast id "	+ podcastId + " ------");
    	Podcast  podcast = podcastService.getPodcastById(podcastId);
    	//add the last episodes to be displayed under the podcast metadata
    	List<Episode> lastEpisodes = null;
    	if(podcast.getEpisodes().size() > 6){
    		lastEpisodes = podcast.getEpisodes().subList(1, 6);
    	} else {
    		lastEpisodes = podcast.getEpisodes();
    	model.addAttribute("lastEpisodes", lastEpisodes);
    	model.addAttribute("nr_divs_with_ratings", lastEpisodes.size());
    	if(podcast.getRating() == null) podcast.setRating(10f);
    	model.addAttribute("roundedRatingScore", Math.round(podcast.getRating()));
    	model.addAttribute("podcast", podcast);
    	SitePreference currentSitePreference = SitePreferenceUtils.getCurrentSitePreference(httpRequest);
    	if(currentSitePreference.isMobile() || currentSitePreference.isTablet()){
    		return "m_podcastDetails";
    	} else {
    		return "podcastDetails";

    The podcast bean is supplied by backend services (line 8) and after that is added to the model (line 21) to be rendered in Tiles. The return value of the method – podcastDetails – is the same as the name for the Tiles definition that renders the page <definition name="podcastDetails" extends="defaultTemplate">

    Note: To better understand the site preference part (lines 23-28) see my post Going mobile with Spring mobile and responsive web design

    The same mechanism is valid for filling the value of the <meta name="description"/> tag. Well, that’s it. Thanks for sharing.

    Octocat Source code for this post is available on Github - is an open source project.


    1. Spring MVC and Apache tiles integration example
    2. Apache Tiles homepage
    3. Apache Tiles – Expression Language support
    4. Site title and description – Google Webmaster Tools help
    5. Search engine optimization starter guide – Google
    6. [Video] Google & SEO Friendly Page Titles
    7. WordPress SEO
    Podcastpedia image

    Adrian Matei

    Creator of and, computer science engineer, husband, father, curious and passionate about science, computers, software, education, economics, social equity, philosophy - but these are just outside labels and not that important, deep inside we are all just consciousness, right?

    How to get the title of a remote web page using javascript and NodeJS

    This post presents how to use web scraping with Cheerio in a NodeJS backend to retrieve the title of a bookmark added in Continue reading

    How to use Showdown in Angular and NodeJS

    Published on February 21, 2017

    Sharing coding bookmarks

    Published on February 11, 2017