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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot + Yauaa: Ultra‑Precise Parsing of Client Device, OS, and Browser Info

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.0

4. 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootCache ConfigurationUser-AgentDevice DetectionYauaa
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.