BuddyPress extended profile fields look like user meta and are not. They live in their own tables — bp_xprofile_fields for the definitions, bp_xprofile_data for one row per field per user — so get_user_meta() returns an empty string for every one of them and offers no hint as to why.
// always empty: xprofile does not touch wp_usermeta
get_user_meta( $user_id, 'Department', true );
// the actual lookup, by field name or field id
bp_get_profile_field_data( array(
'field' => 'Department',
'user_id' => $user_id,
) );
// every field a user has filled in, grouped as the admin arranged them
if ( bp_has_profile( array( 'user_id' => $user_id ) ) ) {
while ( bp_profile_groups() ) { bp_the_profile_group();
while ( bp_profile_fields() ) { bp_the_profile_field();
echo bp_get_the_profile_field_name() . ': ' . bp_get_the_profile_field_value();
}
}
}
Looking a field up by name is convenient, costs a query on every call, and breaks the day an administrator renames it in the admin screen — resolve it once with xprofile_get_field_id_from_name(), keep the id in your own option and use that. The thing that catches people writing reports is that multi-value field types store a serialised array in the value column, so a straight SQL query over the table returns a:2:{...} rather than anything a spreadsheet will accept. And a field marked as required is only enforced by the registration form, so users created by an import or by wp_insert_user() have no value at all and nothing complains.