Spring Boot + Yauaa: Ultra‑Precise Parsing of Client Device, OS, and Browser Info
This article walks through using the Yauaa library in Spring Boot 3.5.0 to extract detailed client‑side information—device class, operating system, and browser—from the User‑Agent header, covering basic bean setup, advanced cache configuration, field selection, and device‑based routing examples.
1. Introduction
Web applications often need to identify the hardware and software characteristics of the requestor (device type, OS version, browser engine) to enable adaptive rendering or feature gating. The HTTP User-Agent header carries this fingerprint, but its free‑form text presents two main parsing challenges: fragmented structures across browsers and nested fields that combine multiple pieces of information.
2. Yauaa library
The open‑source library Yauaa extracts as many fields as possible from a given User‑Agent string and, when available, combines them with Client Hints. Client Hints specification: https://wicg.github.io/ua-client-hints/
3. Environment
Spring Boot version:
3.5.04. Dependency
<dependency>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
<version>7.32.0</version>
</dependency>5. Basic usage
Configure UserAgentAnalyzer as a Spring bean:
@Configuration
public class UserAgentConfig {
@Bean
UserAgentAnalyzer userAgentAnalyzer() {
UserAgentAnalyzer uaa = UserAgentAnalyzer
.newBuilder()
.hideMatcherLoadStats()
.withCache(10000)
.build();
return uaa;
}
}Inject the bean into a controller and parse the incoming header:
private final UserAgentAnalyzer uaa;
public UserAgentController(UserAgentAnalyzer uaa) { this.uaa = uaa; }
@GetMapping
public ResponseEntity<?> useragent(@RequestHeader("User-Agent") String userAgent) {
UserAgent agent = uaa.parse(userAgent);
Map<String, Object> ret = new HashMap<>();
for (String fieldName : agent.getAvailableFieldNamesSorted()) {
ret.put(fieldName, agent.getValue(fieldName));
}
return ResponseEntity.ok(ret);
}6. Advanced configuration
6.1 Custom cache implementation
Since version 6.7 you can supply a CacheInstantiator to control caching. Yauaa assumes the cache is thread‑safe; using a non‑thread‑safe implementation in a multithreaded environment will crash the application. The default cache uses Caffeine from version 6.8 onward.
@Bean
UserAgentAnalyzer userAgentAnalyzer() {
UserAgentAnalyzer uaa = UserAgentAnalyzer
.newBuilder()
.hideMatcherLoadStats()
.withCacheInstantiator(new CacheInstantiator() {
@Override
public Map<String, ImmutableUserAgent> instantiateCache(int cacheSize) {
return cache(cacheSize);
}
})
.withClientHintCacheInstantiator(size ->
Collections.synchronizedMap(new LRUMap<>(size)))
.withCache(10000)
.build();
return uaa;
}
public Map<String, ImmutableUserAgent> cache(int cacheSize) {
return Caffeine.newBuilder()
.maximumSize(10000)
.<String, ImmutableUserAgent>build()
.asMap();
}6.2 Java‑version‑aware caching
Yauaa 7.x runs on Java 8, but the default cache requires Java 11. From version 7.19.0 the library automatically selects LRUMap for Java 8‑10 and Caffeine for Java 11+. To force LRUMap regardless of Java version:
UserAgentAnalyzer uaa = UserAgentAnalyzer
.newBuilder()
.useJava8CompatibleCaching()
.withCache(10000)
.build();6.3 Reducing memory footprint
If only a subset of fields is needed, specify them during builder creation:
UserAgentAnalyzer uaa = UserAgentAnalyzer
.newBuilder()
.withField("DeviceClass")
.withField("AgentNameVersionMajor")
.build();7. Device‑based routing example
Assume a requirement to serve mobile devices only and block non‑mobile traffic. The controller checks the DeviceClass field parsed by Yauaa:
@Controller
@RequestMapping("/useragent/index")
public class IndexController {
private static final List<String> SUPPORTED_MOBILE_DEVICE = List.of("Mobile", "Tablet", "Phone");
private final UserAgentAnalyzer uaa;
public IndexController(UserAgentAnalyzer uaa) { this.uaa = uaa; }
@GetMapping
public ModelAndView homePage(@RequestHeader(HttpHeaders.USER_AGENT) String userAgentString) {
UserAgent userAgent = uaa.parse(userAgentString);
String deviceClass = userAgent.getValue(UserAgent.DEVICE_CLASS);
boolean isMobile = SUPPORTED_MOBILE_DEVICE.contains(deviceClass);
if (isMobile) {
return new ModelAndView("mobile");
}
return new ModelAndView("error/403");
}
}Static resources are configured in application.yml (or application.properties) as follows:
spring:
mvc:
view:
prefix: "/"
suffix: .html
static-path-pattern: /**
web:
resources:
static-locations: classpath:/pages/8. Verification
Running the application and accessing the endpoint from a mobile browser returns the mobile view, while a desktop browser receives a 403 page. The JSON response and routing outcome are shown in the following screenshots.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
