skip to content

In `allure-java`, a `@TmsLink` or `@Issue` annotation records only a ticket id. Which property turns that id into a clickable URL, and what does the report show when the property is missing?

level: middleimportance: must knowfreq 66%

answer

  1. the id alone is not a url
  2. one property per link type
  3. the placeholder is empty braces
  4. resolved when the result is written
  5. unset means null url, plain text

basics

~20 s

allure-java derives the url from allure.link.tms.pattern or allure.link.issue.pattern in allure.properties, substituting the recorded id for the {} placeholder. With no matching pattern the url stays null, so the report shows the id as plain unclickable text.

solid answer

~40 s

The property name is generated from the link type -- `allure.link.<type>.pattern` -- so `allure.link.issue.pattern`, `allure.link.tms.pattern` and `allure.link.custom.pattern` are all real, and any type string you invent gets its own key. `allure-java` reads it from `allure.properties` on the test run's classpath and substitutes the recorded id for the `{}` placeholder. Two shortcuts bypass it: an explicit `url` on `@Link` wins outright, and a value that already parses as an http or https URL is used as-is. When no pattern is found the derivation returns null, the `Link` is written with a null `url`, and the report prints the id with nothing to click -- no error, no warning. Resolution happens at **write** time, so adding the property to the job that generates the report changes nothing.

code

properties · 4 lines
properties
allure.results.directory=target/allure-results
allure.link.issue.pattern=https://tracker.example.com/browse/{}
allure.link.tms.pattern=https://tms.example.com/case/{}
allure.link.custom.pattern=https://wiki.example.com/{}

go deeper

for a junior

Recall that an issue or tms annotation stores an id, and that a separate configuration property is what turns that id into an address the reader can click.

for a middle

Explain that the property name is assembled from the link type, name the placeholder, and say plainly that a missing pattern yields a null url rather than a failure.

for a senior

Be ready to diagnose dead links across a fleet of suites: per-type keys, classpath visibility of the properties file, and the fact that regenerating an old results directory cannot repair them.

for a principal

Decide where link templates live so they survive team and tracker changes, and be able to argue why a value baked in at write time is or is not the right coupling for your organisation.

## What the annotation actually records `@Issue("BUG-88")` and `@TmsLink("TC-431")` look as though they create a hyperlink. They do not. The annotation records a **value** -- a bare id -- and the link is assembled from it later. When `allure-java` builds the `Link` object it has to decide what goes in the `url` field, and it works through three possibilities in order: 1. **An explicit url on the annotation wins.** `@Link` accepts a url directly; if one is present, it is used and nothing else is consulted. 2. **A value that is already a url is used as-is.** If the recorded value parses as an `http` or `https` URL with a host, that value becomes the url. This is why pasting a full address into an annotation "just works" and hides the mechanism from people who have never pasted an id. 3. **Otherwise the pattern is applied.** The value is substituted into a template read from configuration. Step three is the one worth knowing, because it is the only one that scales past a handful of tests. ## The property name is generated, not fixed The template lives in `allure.properties`, on the classpath of the JVM running the tests, and its key is **built from the link's type**: ```properties allure.link.issue.pattern=https://tracker.example.com/browse/{} allure.link.tms.pattern=https://tms.example.com/case/{} allure.link.custom.pattern=https://wiki.example.com/{} ``` So `allure.link.issue.pattern`, `allure.link.tms.pattern` and `allure.link.custom.pattern` are all real keys -- and so is a key for any type string you invent, because the name is assembled from the type rather than looked up in a fixed list. Give a link a type of your own and the key consulted for it has a name of exactly the same generated shape. The placeholder is **`{}`** -- an empty pair of braces, with no name inside. Every occurrence in the template is replaced by the link's recorded value. ## What happens when the property is absent Nothing loud. The lookup returns nothing, the derivation returns null, and the `Link` is written with its `name` and its `type` intact and `url` set to null. In the generated report the id appears as ordinary text. There is no exception, no warning line, and no invalid file -- a link without a url is a perfectly well-formed link. That silence is the whole reason this question gets asked. The symptom people report is "the tickets aren't clickable", and the instinct is to look at the report; but the report is faithfully rendering what it was given. ## Write time, not generate time **The url is baked into the result file at the moment the result is written.** This has one very practical consequence and it is the part candidates most often miss: - Adding the property to the CI step that runs the generator **changes nothing**. By then the result files already contain null urls, and nothing re-derives them. - Regenerating an old results directory after fixing the configuration also changes nothing, for the same reason. - The property has to be visible to the JVM that ran the tests -- in practice, `allure.properties` on the test run's classpath, so it ships with the suite rather than with the pipeline. The same fact explains why the two Allure majors are not symmetric here. The pattern is a **writer-side** mechanism in `allure-java`, which feeds both. Allure 2 consumes the already-resolved url. Allure 3's report model likewise carries a link as a name, a url and a type, with the url already resolved -- it has no reader-side pattern property of its own to fall back on. Either way, resolution is upstream of the report. ## Diagnosing it A short checklist that separates the two things people confuse: - **Some links work and some do not.** Look at the types. One type has a pattern configured and another does not; the key is per type, not per run. - **No links work anywhere.** Look at whether `allure.properties` is on the test run's classpath at all, rather than sitting next to the pipeline definition. - **Links work locally and not in CI.** The file is probably a local resource that is not packaged into the artefact the CI job runs from. - **The url is wrong rather than missing.** The template is being applied, so the property is being found; the template itself is what needs fixing. ## Boundaries worth keeping straight The pattern decides **how an id becomes an address**. It does not decide which id belongs on a test, it does not check that the id exists, and it does not fetch anything. A url built from a deleted ticket is still built and still rendered; the report has no idea the far end is gone. Everything the mechanism knows is one string from the annotation and one template from a properties file.

  • Your CI job sets the pattern on the generate step and the ids are still not clickable. Why?
    Because the url is derived when the result file is written, not when the report is generated. By generate time the `Link` already carries a null `url` and nothing re-derives it. The property has to be visible to the JVM that ran the tests, which in practice means `allure.properties` on that run's classpath.
  • What happens if the annotation value is already a full URL?
    `allure-java` checks whether the recorded value parses as an http or https URL with a host. If it does, that value becomes the link's `url` directly and no pattern is consulted at all. An explicit `url` attribute on `@Link` also wins over the pattern.

saying these in an interview costs you the question

  • Says the report generator resolves the ticket url
  • Expects an error when the pattern is missing
  • Thinks one property covers every link type
  • Uses a named placeholder instead of empty braces